/*-------------------------------------------------------------------------
 *
 * explain.c
 *      Explain query execution plans
 *
 * Portions Copyright (c) 2012-2014, TransLattice, Inc.
 * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994-5, Regents of the University of California
 *
 * This source code file contains modifications made by THL A29 Limited ("Tencent Modifications").
 * All Tencent Modifications are Copyright (C) 2023 THL A29 Limited.
 *
 * IDENTIFICATION
 *      src/backend/commands/explain.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/xact.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
#include "executor/nodeHash.h"
#include "foreign/fdwapi.h"
#include "nodes/extensible.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteHandler.h"
#include "storage/bufmgr.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/json.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/ruleutils.h"
#include "utils/snapmgr.h"
#include "utils/tuplesort.h"
#include "utils/typcache.h"
#include "utils/xml.h"
#ifdef PGXC
#include "catalog/pgxc_node.h"
#include "nodes/makefuncs.h"
#include "pgxc/pgxcnode.h"
#include "pgxc/planner.h"
#include "pgxc/execRemote.h"
#endif
#ifdef __OPENTENBASE__
#include "commands/explain_dist.h"
#include "commands/vacuum.h"
#endif

/* Hook for plugins to get control in ExplainOneQuery() */
ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL;

/* Hook for plugins to get control in explain_get_index_name() */
explain_get_index_name_hook_type explain_get_index_name_hook = NULL;


/* OR-able flags for ExplainXMLTag() */
#define X_OPENING 0
#define X_CLOSING 1
#define X_CLOSE_IMMEDIATE 2
#define X_NOWHITESPACE 4

static void ExplainOneQuery(Query *query, int cursorOptions,
                IntoClause *into, ExplainState *es,
                const char *queryString, ParamListInfo params,
                QueryEnvironment *queryEnv);
static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
                ExplainState *es);
static double elapsed_time(instr_time *starttime);
static bool ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used);
static void ExplainNode(PlanState *planstate, List *ancestors,
            const char *relationship, const char *plan_name,
            ExplainState *es);
static void show_plan_tlist(PlanState *planstate, List *ancestors,
                ExplainState *es);
static void show_expression(Node *node, const char *qlabel,
                PlanState *planstate, List *ancestors,
                bool useprefix, ExplainState *es);
static void show_qual(List *qual, const char *qlabel,
          PlanState *planstate, List *ancestors,
          bool useprefix, ExplainState *es);
static void show_scan_qual(List *qual, const char *qlabel,
               PlanState *planstate, List *ancestors,
               ExplainState *es);
static void show_upper_qual(List *qual, const char *qlabel,
                PlanState *planstate, List *ancestors,
                ExplainState *es);
static void show_sort_keys(SortState *sortstate, List *ancestors,
               ExplainState *es);
static void show_simple_sort_keys(RemoteSubplanState *remotestate,
               List *ancestors, ExplainState *es);
static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
                       ExplainState *es);
static void show_agg_keys(AggState *astate, List *ancestors,
              ExplainState *es);
static void show_grouping_sets(PlanState *planstate, Agg *agg,
                   List *ancestors, ExplainState *es);
static void show_grouping_set_keys(PlanState *planstate,
                       Agg *aggnode, Sort *sortnode,
                       List *context, bool useprefix,
                       List *ancestors, ExplainState *es);
static void show_group_keys(GroupState *gstate, List *ancestors,
                ExplainState *es);
static void show_sort_group_keys(PlanState *planstate, const char *qlabel,
                     int nkeys, AttrNumber *keycols,
                     Oid *sortOperators, Oid *collations, bool *nullsFirst,
                     List *ancestors, ExplainState *es);
static void show_sortorder_options(StringInfo buf, Node *sortexpr,
                       Oid sortOperator, Oid collation, bool nullsFirst);
static void show_tablesample(TableSampleClause *tsc, PlanState *planstate,
                 List *ancestors, ExplainState *es);
static void show_sort_info(SortState *sortstate, ExplainState *es);
static void show_hash_info(HashState *hashstate, ExplainState *es);
static void show_tidbitmap_info(BitmapHeapScanState *planstate,
                    ExplainState *es);
static void show_instrumentation_count(const char *qlabel, int which,
                           PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
                        ExplainState *es);
static void ExplainScanTarget(Scan *plan, ExplainState *es);
static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
                      ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstates,
                   List *ancestors, ExplainState *es);
static void ExplainSubPlans(List *plans, List *ancestors,
                const char *relationship, ExplainState *es);
static void ExplainCustomChildren(CustomScanState *css,
                      List *ancestors, ExplainState *es);
static void ExplainProperty(const char *qlabel, const char *value,
                bool numeric, ExplainState *es);
static void ExplainOpenGroup(const char *objtype, const char *labelname,
                 bool labeled, ExplainState *es);
static void ExplainCloseGroup(const char *objtype, const char *labelname,
                  bool labeled, ExplainState *es);
static void ExplainDummyGroup(const char *objtype, const char *labelname,
                  ExplainState *es);
#ifdef PGXC
static void ExplainExecNodes(ExecNodes *en, ExplainState *es);
static void ExplainRemoteQuery(RemoteQuery *plan, PlanState *planstate,
                                List *ancestors, ExplainState *es);
static char **StrSplit(const char *str, const char *delimiter, int *n);
#endif
static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es);
static void ExplainJSONLineEnding(ExplainState *es);
static void ExplainYAMLLineStarting(ExplainState *es);
static void escape_yaml(StringInfo buf, const char *str);



/*
 * ExplainQuery -
 *      execute an EXPLAIN command
 */
void
ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString,
             ParamListInfo params, QueryEnvironment *queryEnv,
             DestReceiver *dest)
{// #lizard forgives
    ExplainState *es = NewExplainState();
    TupOutputState *tstate;
    List       *rewritten;
    ListCell   *lc;
    bool        timing_set = false;
    bool        summary_set = false;

    /* Parse options list. */
    foreach(lc, stmt->options)
    {
        DefElem    *opt = (DefElem *) lfirst(lc);

        if (strcmp(opt->defname, "analyze") == 0)
            es->analyze = defGetBoolean(opt);
        else if (strcmp(opt->defname, "verbose") == 0)
            es->verbose = defGetBoolean(opt);
        else if (strcmp(opt->defname, "costs") == 0)
            es->costs = defGetBoolean(opt);
        else if (strcmp(opt->defname, "buffers") == 0)
            es->buffers = defGetBoolean(opt);
#ifdef PGXC
        else if (strcmp(opt->defname, "nodes") == 0)
            es->nodes = defGetBoolean(opt);
        else if (strcmp(opt->defname, "num_nodes") == 0)
            es->num_nodes = defGetBoolean(opt);
#endif /* PGXC */
        else if (strcmp(opt->defname, "timing") == 0)
        {
            timing_set = true;
            es->timing = defGetBoolean(opt);
        }
        else if (strcmp(opt->defname, "summary") == 0)
        {
            summary_set = true;
            es->summary = defGetBoolean(opt);
        }
        else if (strcmp(opt->defname, "format") == 0)
        {
            char       *p = defGetString(opt);

            if (strcmp(p, "text") == 0)
                es->format = EXPLAIN_FORMAT_TEXT;
            else if (strcmp(p, "xml") == 0)
                es->format = EXPLAIN_FORMAT_XML;
            else if (strcmp(p, "json") == 0)
                es->format = EXPLAIN_FORMAT_JSON;
            else if (strcmp(p, "yaml") == 0)
                es->format = EXPLAIN_FORMAT_YAML;
            else
                ereport(ERROR,
                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                         errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
                                opt->defname, p),
                         parser_errposition(pstate, opt->location)));
        }
        else
            ereport(ERROR,
                    (errcode(ERRCODE_SYNTAX_ERROR),
                     errmsg("unrecognized EXPLAIN option \"%s\"",
                            opt->defname),
                     parser_errposition(pstate, opt->location)));
    }

    if (es->buffers && !es->analyze)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                 errmsg("EXPLAIN option BUFFERS requires ANALYZE")));

    /* if the timing was not set explicitly, set default value */
    es->timing = (timing_set) ? es->timing : es->analyze;

    /* check that timing is used with EXPLAIN ANALYZE */
    if (es->timing && !es->analyze)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                 errmsg("EXPLAIN option TIMING requires ANALYZE")));

    /* if the summary was not set explicitly, set default value */
    es->summary = (summary_set) ? es->summary : es->analyze;

    /*
     * Parse analysis was done already, but we still have to run the rule
     * rewriter.  We do not do AcquireRewriteLocks: we assume the query either
     * came straight from the parser, or suitable locks were acquired by
     * plancache.c.
     *
     * Because the rewriter and planner tend to scribble on the input, we make
     * a preliminary copy of the source querytree.  This prevents problems in
     * the case that the EXPLAIN is in a portal or plpgsql function and is
     * executed repeatedly.  (See also the same hack in DECLARE CURSOR and
     * PREPARE.)  XXX FIXME someday.
     */
    rewritten = QueryRewrite(castNode(Query, copyObject(stmt->query)));

    /* emit opening boilerplate */
    ExplainBeginOutput(es);

    if (rewritten == NIL)
    {
        /*
         * In the case of an INSTEAD NOTHING, tell at least that.  But in
         * non-text format, the output is delimited, so this isn't necessary.
         */
        if (es->format == EXPLAIN_FORMAT_TEXT)
            appendStringInfoString(es->str, "Query rewrites to nothing\n");
    }
    else
    {
        ListCell   *l;

        /* Explain every plan */
        foreach(l, rewritten)
        {
            ExplainOneQuery(lfirst_node(Query, l),
                            CURSOR_OPT_PARALLEL_OK, NULL, es,
                            queryString, params, queryEnv);

            /* Separate plans with an appropriate separator */
            if (lnext(l) != NULL)
                ExplainSeparatePlans(es);
        }
    }

    /* emit closing boilerplate */
    ExplainEndOutput(es);
    Assert(es->indent == 0);

    /* output tuples */
    tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
    if (es->format == EXPLAIN_FORMAT_TEXT)
        do_text_output_multiline(tstate, es->str->data);
    else
        do_text_output_oneline(tstate, es->str->data);
    end_tup_output(tstate);

    pfree(es->str->data);
}

/*
 * Create a new ExplainState struct initialized with default options.
 */
ExplainState *
NewExplainState(void)
{
    ExplainState *es = (ExplainState *) palloc0(sizeof(ExplainState));

    /* Set default options (most fields can be left as zeroes). */
    es->costs = true;
#ifdef PGXC
    es->nodes = true;
#endif /* PGXC */
    /* Prepare output buffer. */
    es->str = makeStringInfo();

    return es;
}

/*
 * ExplainResultDesc -
 *      construct the result tupledesc for an EXPLAIN
 */
TupleDesc
ExplainResultDesc(ExplainStmt *stmt)
{
    TupleDesc    tupdesc;
    ListCell   *lc;
    Oid            result_type = TEXTOID;

    /* Check for XML format option */
    foreach(lc, stmt->options)
    {
        DefElem    *opt = (DefElem *) lfirst(lc);

        if (strcmp(opt->defname, "format") == 0)
        {
            char       *p = defGetString(opt);

            if (strcmp(p, "xml") == 0)
                result_type = XMLOID;
            else if (strcmp(p, "json") == 0)
                result_type = JSONOID;
            else
                result_type = TEXTOID;
            /* don't "break", as ExplainQuery will use the last value */
        }
    }

    /* Need a tuple descriptor representing a single TEXT or XML column */
    tupdesc = CreateTemplateTupleDesc(1, false);
    TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
                       result_type, -1, 0);
    return tupdesc;
}

/*
 * ExplainOneQuery -
 *      print out the execution plan for one Query
 *
 * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
 */
static void
ExplainOneQuery(Query *query, int cursorOptions,
                IntoClause *into, ExplainState *es,
                const char *queryString, ParamListInfo params,
                QueryEnvironment *queryEnv)
{
    /* planner will not cope with utility statements */
    if (query->commandType == CMD_UTILITY)
    {
        /*
         * If we are running EXPLAIN ANALYZE, transform the CTAS such that the
         * target table is created first and select result is inserted into the
         * table. The EXPLAIN ANALYZE would really just show the plan for the
         * INSERT INTO generated by QueryRewriteCTAS, but that's OK.
         */
        if (es->analyze && IsA(query->utilityStmt, CreateTableAsStmt))
        {
            List *rewritten = QueryRewriteCTAS(query);
            Assert(list_length(rewritten) == 1);
            ExplainOneQuery((Query *) linitial(rewritten), cursorOptions,
                    into, es, queryString, params, queryEnv);
        }
        else
            ExplainOneUtility(query->utilityStmt, into, es,
                    queryString, params, queryEnv);
        return;
    }

    /* if an advisor plugin is present, let it manage things */
    if (ExplainOneQuery_hook)
        (*ExplainOneQuery_hook) (query, cursorOptions, into, es,
                                 queryString, params);
    else
    {
        PlannedStmt *plan;
        instr_time    planstart,
                    planduration;

        INSTR_TIME_SET_CURRENT(planstart);

        /* plan the query */
        plan = pg_plan_query(query, cursorOptions, params);

        INSTR_TIME_SET_CURRENT(planduration);
        INSTR_TIME_SUBTRACT(planduration, planstart);

        /* run it (if needed) and produce output */
        ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
                       &planduration);
    }
}

/*
 * ExplainOneUtility -
 *      print out the execution plan for one utility statement
 *      (In general, utility statements don't have plans, but there are some
 *      we treat as special cases)
 *
 * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
 *
 * This is exported because it's called back from prepare.c in the
 * EXPLAIN EXECUTE case.
 */
void
ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
                  const char *queryString, ParamListInfo params,
                  QueryEnvironment *queryEnv)
{// #lizard forgives
    if (utilityStmt == NULL)
        return;

    if (IsA(utilityStmt, CreateTableAsStmt))
    {
        /*
         * We have to rewrite the contained SELECT and then pass it back to
         * ExplainOneQuery.  It's probably not really necessary to copy the
         * contained parsetree another time, but let's be safe.
         *
         * Like ExecCreateTableAs, disallow parallelism in the plan.
         */
        CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
        List       *rewritten;

        rewritten = QueryRewrite(castNode(Query, copyObject(ctas->query)));
        Assert(list_length(rewritten) == 1);
        ExplainOneQuery(linitial_node(Query, rewritten),
                        0, ctas->into, es,
                        queryString, params, queryEnv);
    }
    else if (IsA(utilityStmt, DeclareCursorStmt))
    {
        /*
         * Likewise for DECLARE CURSOR.
         *
         * Notice that if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll
         * actually run the query.  This is different from pre-8.3 behavior
         * but seems more useful than not running the query.  No cursor will
         * be created, however.
         */
        DeclareCursorStmt *dcs = (DeclareCursorStmt *) utilityStmt;
        List       *rewritten;

        rewritten = QueryRewrite(castNode(Query, copyObject(dcs->query)));
        Assert(list_length(rewritten) == 1);
        ExplainOneQuery(linitial_node(Query, rewritten),
                        dcs->options, NULL, es,
                        queryString, params, queryEnv);
    }
    else if (IsA(utilityStmt, ExecuteStmt))
        ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
                            queryString, params, queryEnv);
    else if (IsA(utilityStmt, NotifyStmt))
    {
        if (es->format == EXPLAIN_FORMAT_TEXT)
            appendStringInfoString(es->str, "NOTIFY\n");
        else
            ExplainDummyGroup("Notify", NULL, es);
    }
    else
    {
        if (es->format == EXPLAIN_FORMAT_TEXT)
            appendStringInfoString(es->str,
                                   "Utility statements have no plan structure\n");
        else
            ExplainDummyGroup("Utility Statement", NULL, es);
    }
}

/*
 * ExplainOnePlan -
 *        given a planned query, execute it if needed, and then print
 *        EXPLAIN output
 *
 * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt,
 * in which case executing the query should result in creating that table.
 *
 * This is exported because it's called back from prepare.c in the
 * EXPLAIN EXECUTE case, and because an index advisor plugin would need
 * to call it.
 */
void
ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
               const char *queryString, ParamListInfo params,
               QueryEnvironment *queryEnv, const instr_time *planduration)
{// #lizard forgives
    DestReceiver *dest;
    QueryDesc  *queryDesc;
    instr_time    starttime;
    double        totaltime = 0;
    int            eflags;
    int            instrument_option = 0;

    Assert(plannedstmt->commandType != CMD_UTILITY);

    if (es->analyze && es->timing)
        instrument_option |= INSTRUMENT_TIMER;
    else if (es->analyze)
        instrument_option |= INSTRUMENT_ROWS;

    if (es->buffers)
        instrument_option |= INSTRUMENT_BUFFERS;

    /*
     * We always collect timing for the entire statement, even when node-level
     * timing is off, so we don't look at es->timing here.  (We could skip
     * this if !es->summary, but it's hardly worth the complication.)
     */
    INSTR_TIME_SET_CURRENT(starttime);

    /*
     * Use a snapshot with an updated command ID to ensure this query sees
     * results of any previously executed queries.
     */
    PushCopiedSnapshot(GetActiveSnapshot());
    UpdateActiveSnapshotCommandId();

    /*
     * Normally we discard the query's output, but if explaining CREATE TABLE
     * AS, we'd better use the appropriate tuple receiver.
     */
    if (into)
        dest = CreateIntoRelDestReceiver(into);
    else
        dest = None_Receiver;

    /* Create a QueryDesc for the query */
    queryDesc = CreateQueryDesc(plannedstmt, queryString,
                                GetActiveSnapshot(), InvalidSnapshot,
                                dest, params, queryEnv, instrument_option);

    /* Select execution options */
    if (es->analyze)
        eflags = 0;                /* default run-to-completion flags */
    else
        eflags = EXEC_FLAG_EXPLAIN_ONLY;
    if (into)
        eflags |= GetIntoRelEFlags(into);

    /* call ExecutorStart to prepare the plan for execution */
    ExecutorStart(queryDesc, eflags);

    /* Execute the plan for statistics if asked for */
    if (es->analyze)
    {
        ScanDirection dir;

        /* EXPLAIN ANALYZE CREATE TABLE AS WITH NO DATA is weird */
        if (into && into->skipData)
            dir = NoMovementScanDirection;
        else
            dir = ForwardScanDirection;

        /* run the plan */
        ExecutorRun(queryDesc, dir, 0L, true);

        /* run cleanup too */
        ExecutorFinish(queryDesc);

        /* We can't run ExecutorEnd 'till we're done printing the stats... */
        totaltime += elapsed_time(&starttime);
    }

    ExplainOpenGroup("Query", NULL, true, es);

    /* Create textual dump of plan tree */
    ExplainPrintPlan(es, queryDesc);

    if (es->summary && planduration)
    {
        double        plantime = INSTR_TIME_GET_DOUBLE(*planduration);

        if (es->format == EXPLAIN_FORMAT_TEXT)
            appendStringInfo(es->str, "Planning time: %.3f ms\n",
                             1000.0 * plantime);
        else
            ExplainPropertyFloat("Planning Time", 1000.0 * plantime, 3, es);
    }

    /* Print info about runtime of triggers */
    if (es->analyze)
        ExplainPrintTriggers(es, queryDesc);

    /*
     * Close down the query and free resources.  Include time for this in the
     * total execution time (although it should be pretty minimal).
     */
    INSTR_TIME_SET_CURRENT(starttime);

    ExecutorEnd(queryDesc);

    FreeQueryDesc(queryDesc);

    PopActiveSnapshot();

    /* We need a CCI just in case query expanded to multiple plans */
    if (es->analyze)
        CommandCounterIncrement();

    totaltime += elapsed_time(&starttime);

    /*
     * We only report execution time if we actually ran the query (that is,
     * the user specified ANALYZE), and if summary reporting is enabled (the
     * user can set SUMMARY OFF to not have the timing information included in
     * the output).  By default, ANALYZE sets SUMMARY to true.
     */
    if (es->summary && es->analyze)
    {
        if (es->format == EXPLAIN_FORMAT_TEXT)
            appendStringInfo(es->str, "Execution time: %.3f ms\n",
                             1000.0 * totaltime);
        else
            ExplainPropertyFloat("Execution Time", 1000.0 * totaltime,
                                 3, es);
    }

    ExplainCloseGroup("Query", NULL, true, es);
}

/*
 * ExplainPrintPlan -
 *      convert a QueryDesc's plan tree to text and append it to es->str
 *
 * The caller should have set up the options fields of *es, as well as
 * initializing the output buffer es->str.  Also, output formatting state
 * such as the indent level is assumed valid.  Plan-tree-specific fields
 * in *es are initialized here.
 *
 * NB: will not work on utility statements
 */
void
ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
{
    Bitmapset  *rels_used = NULL;
    PlanState  *ps;

    /* Set up ExplainState fields associated with this plan tree */
    Assert(queryDesc->plannedstmt != NULL);
    es->pstmt = queryDesc->plannedstmt;
    es->rtable = queryDesc->plannedstmt->rtable;
    ExplainPreScanNode(queryDesc->planstate, &rels_used);
    es->rtable_names = select_rtable_names_for_explain(es->rtable, rels_used);
    es->deparse_cxt = deparse_context_for_plan_rtable(es->rtable,
                                                      es->rtable_names);
    es->printed_subplans = NULL;

    /*
     * Sometimes we mark a Gather node as "invisible", which means that it's
     * not displayed in EXPLAIN output.  The purpose of this is to allow
     * running regression tests with force_parallel_mode=regress to get the
     * same results as running the same tests with force_parallel_mode=off.
     */
    ps = queryDesc->planstate;
    if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
        ps = outerPlanState(ps);
    ExplainNode(ps, NIL, NULL, NULL, es);
}

/*
 * ExplainPrintTriggers -
 *      convert a QueryDesc's trigger statistics to text and append it to
 *      es->str
 *
 * The caller should have set up the options fields of *es, as well as
 * initializing the output buffer es->str.  Other fields in *es are
 * initialized here.
 */
void
ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc)
{
    ResultRelInfo *rInfo;
    bool        show_relname;
    int            numrels = queryDesc->estate->es_num_result_relations;
    List       *targrels = queryDesc->estate->es_trig_target_relations;
    int            nr;
    ListCell   *l;

    ExplainOpenGroup("Triggers", "Triggers", false, es);

    show_relname = (numrels > 1 || targrels != NIL);
    rInfo = queryDesc->estate->es_result_relations;
    for (nr = 0; nr < numrels; rInfo++, nr++)
        report_triggers(rInfo, show_relname, es);

    foreach(l, targrels)
    {
        rInfo = (ResultRelInfo *) lfirst(l);
        report_triggers(rInfo, show_relname, es);
    }

    ExplainCloseGroup("Triggers", "Triggers", false, es);
}

/*
 * ExplainQueryText -
 *      add a "Query Text" node that contains the actual text of the query
 *
 * The caller should have set up the options fields of *es, as well as
 * initializing the output buffer es->str.
 *
 */
void
ExplainQueryText(ExplainState *es, QueryDesc *queryDesc)
{
    if (queryDesc->sourceText)
        ExplainPropertyText("Query Text", queryDesc->sourceText, es);
}

/*
 * report_triggers -
 *        report execution stats for a single relation's triggers
 */
static void
report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es)
{// #lizard forgives
    int            nt;

    if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument)
        return;
    for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++)
    {
        Trigger    *trig = rInfo->ri_TrigDesc->triggers + nt;
        Instrumentation *instr = rInfo->ri_TrigInstrument + nt;
        char       *relname;
        char       *conname = NULL;

        /* Must clean up instrumentation state */
        InstrEndLoop(instr);

        /*
         * We ignore triggers that were never invoked; they likely aren't
         * relevant to the current query type.
         */
        if (instr->ntuples == 0)
            continue;

        ExplainOpenGroup("Trigger", NULL, true, es);

        relname = RelationGetRelationName(rInfo->ri_RelationDesc);
        if (OidIsValid(trig->tgconstraint))
            conname = get_constraint_name(trig->tgconstraint);

        /*
         * In text format, we avoid printing both the trigger name and the
         * constraint name unless VERBOSE is specified.  In non-text formats
         * we just print everything.
         */
        if (es->format == EXPLAIN_FORMAT_TEXT)
        {
            if (es->verbose || conname == NULL)
                appendStringInfo(es->str, "Trigger %s", trig->tgname);
            else
                appendStringInfoString(es->str, "Trigger");
            if (conname)
                appendStringInfo(es->str, " for constraint %s", conname);
            if (show_relname)
                appendStringInfo(es->str, " on %s", relname);
            if (es->timing)
                appendStringInfo(es->str, ": time=%.3f calls=%.0f\n",
                                 1000.0 * instr->total, instr->ntuples);
            else
                appendStringInfo(es->str, ": calls=%.0f\n", instr->ntuples);
        }
        else
        {
            ExplainPropertyText("Trigger Name", trig->tgname, es);
            if (conname)
                ExplainPropertyText("Constraint Name", conname, es);
            ExplainPropertyText("Relation", relname, es);
            if (es->timing)
                ExplainPropertyFloat("Time", 1000.0 * instr->total, 3, es);
            ExplainPropertyFloat("Calls", instr->ntuples, 0, es);
        }

        if (conname)
            pfree(conname);

        ExplainCloseGroup("Trigger", NULL, true, es);
    }
}

/* Compute elapsed time in seconds since given timestamp */
static double
elapsed_time(instr_time *starttime)
{
    instr_time    endtime;

    INSTR_TIME_SET_CURRENT(endtime);
    INSTR_TIME_SUBTRACT(endtime, *starttime);
    return INSTR_TIME_GET_DOUBLE(endtime);
}

/*
 * ExplainPreScanNode -
 *      Prescan the planstate tree to identify which RTEs are referenced
 *
 * Adds the relid of each referenced RTE to *rels_used.  The result controls
 * which RTEs are assigned aliases by select_rtable_names_for_explain.
 * This ensures that we don't confusingly assign un-suffixed aliases to RTEs
 * that never appear in the EXPLAIN output (such as inheritance parents).
 */
static bool
ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
{// #lizard forgives
    Plan       *plan = planstate->plan;

    switch (nodeTag(plan))
    {
        case T_SeqScan:
        case T_SampleScan:
        case T_IndexScan:
        case T_IndexOnlyScan:
        case T_BitmapHeapScan:
        case T_TidScan:
        case T_SubqueryScan:
        case T_FunctionScan:
        case T_TableFuncScan:
        case T_ValuesScan:
        case T_CteScan:
        case T_NamedTuplestoreScan:
        case T_WorkTableScan:
            *rels_used = bms_add_member(*rels_used,
                                        ((Scan *) plan)->scanrelid);
            break;
        case T_ForeignScan:
            *rels_used = bms_add_members(*rels_used,
                                         ((ForeignScan *) plan)->fs_relids);
            break;
        case T_CustomScan:
            *rels_used = bms_add_members(*rels_used,
                                         ((CustomScan *) plan)->custom_relids);
            break;
        case T_ModifyTable:
            *rels_used = bms_add_member(*rels_used,
                                        ((ModifyTable *) plan)->nominalRelation);
            if (((ModifyTable *) plan)->exclRelRTI)
                *rels_used = bms_add_member(*rels_used,
                                            ((ModifyTable *) plan)->exclRelRTI);
            break;
        default:
            break;
    }

    return planstate_tree_walker(planstate, ExplainPreScanNode, rels_used);
}

/*
 * ExplainNode -
 *      Appends a description of a plan tree to es->str
 *
 * planstate points to the executor state node for the current plan node.
 * We need to work from a PlanState node, not just a Plan node, in order to
 * get at the instrumentation data (if any) as well as the list of subplans.
 *
 * ancestors is a list of parent PlanState nodes, most-closely-nested first.
 * These are needed in order to interpret PARAM_EXEC Params.
 *
 * relationship describes the relationship of this plan node to its parent
 * (eg, "Outer", "Inner"); it can be null at top level.  plan_name is an
 * optional name to be attached to the node.
 *
 * In text format, es->indent is controlled in this function since we only
 * want it to change at plan-node boundaries.  In non-text formats, es->indent
 * corresponds to the nesting depth of logical output groups, and therefore
 * is controlled by ExplainOpenGroup/ExplainCloseGroup.
 */
static void
ExplainNode(PlanState *planstate, List *ancestors,
            const char *relationship, const char *plan_name,
            ExplainState *es)
{// #lizard forgives
    Plan       *plan = NULL;
    const char *pname = NULL;            /* node type name for text output */
    const char *sname = NULL;            /* node type name for non-text output */
    const char *strategy = NULL;
    const char *partialmode = NULL;
    const char *operation = NULL;
    const char *custom_name = NULL;
    int            save_indent = es->indent;
    bool        haschildren;

#ifdef __OPENTENBASE__
    if(!planstate) return;
    
    plan = planstate->plan;
#endif

    switch (nodeTag(plan))
    {
        case T_Result:
            pname = sname = "Result";
            break;
        case T_ProjectSet:
            pname = sname = "ProjectSet";
            break;
        case T_ModifyTable:
            sname = "ModifyTable";
            switch (((ModifyTable *) plan)->operation)
            {
                case CMD_INSERT:
                    pname = operation = "Insert";
                    break;
                case CMD_UPDATE:
                    pname = operation = "Update";
                    break;
                case CMD_DELETE:
                    pname = operation = "Delete";
                    break;
                default:
                    pname = "???";
                    break;
            }
            break;
        case T_Append:
            pname = sname = "Append";
            break;
        case T_MergeAppend:
            pname = sname = "Merge Append";
            break;
        case T_RecursiveUnion:
            pname = sname = "Recursive Union";
            break;
        case T_BitmapAnd:
            pname = sname = "BitmapAnd";
            break;
        case T_BitmapOr:
            pname = sname = "BitmapOr";
            break;
        case T_NestLoop:
            pname = sname = "Nested Loop";
            break;
        case T_MergeJoin:
            pname = "Merge";    /* "Join" gets added by jointype switch */
            sname = "Merge Join";
            break;
        case T_HashJoin:
            pname = "Hash";        /* "Join" gets added by jointype switch */
            sname = "Hash Join";
            break;
        case T_SeqScan:
            pname = sname = "Seq Scan";
            break;
        case T_SampleScan:
            pname = sname = "Sample Scan";
            break;
        case T_Gather:
            pname = sname = "Gather";
            break;
        case T_GatherMerge:
            pname = sname = "Gather Merge";
            break;
        case T_IndexScan:
            pname = sname = "Index Scan";
            break;
        case T_IndexOnlyScan:
            pname = sname = "Index Only Scan";
            break;
        case T_BitmapIndexScan:
            pname = sname = "Bitmap Index Scan";
            break;
        case T_BitmapHeapScan:
            pname = sname = "Bitmap Heap Scan";
            break;
        case T_TidScan:
            pname = sname = "Tid Scan";
            break;
        case T_SubqueryScan:
            pname = sname = "Subquery Scan";
            break;
        case T_FunctionScan:
            pname = sname = "Function Scan";
            break;
        case T_TableFuncScan:
            pname = sname = "Table Function Scan";
            break;
        case T_ValuesScan:
            pname = sname = "Values Scan";
            break;
        case T_CteScan:
            pname = sname = "CTE Scan";
            break;
        case T_NamedTuplestoreScan:
            pname = sname = "Named Tuplestore Scan";
            break;
        case T_WorkTableScan:
            pname = sname = "WorkTable Scan";
            break;
#ifdef PGXC
        case T_RemoteQuery:
            pname = sname = "Remote Fast Query Execution";
            break;
#endif
        case T_ForeignScan:
            sname = "Foreign Scan";
            switch (((ForeignScan *) plan)->operation)
            {
                case CMD_SELECT:
                    pname = "Foreign Scan";
                    operation = "Select";
                    break;
                case CMD_INSERT:
                    pname = "Foreign Insert";
                    operation = "Insert";
                    break;
                case CMD_UPDATE:
                    pname = "Foreign Update";
                    operation = "Update";
                    break;
                case CMD_DELETE:
                    pname = "Foreign Delete";
                    operation = "Delete";
                    break;
                default:
                    pname = "???";
                    break;
            }
            break;
#ifdef XCP
        case T_RemoteSubplan:
            pname = sname = "Remote Subquery Scan";
            break;
#endif /* XCP */
        case T_CustomScan:
            sname = "Custom Scan";
            custom_name = ((CustomScan *) plan)->methods->CustomName;
            if (custom_name)
                pname = psprintf("Custom Scan (%s)", custom_name);
            else
                pname = sname;
            break;
        case T_Material:
            pname = sname = "Materialize";
            break;
        case T_Sort:
            pname = sname = "Sort";
            break;
        case T_Group:
            pname = sname = "Group";
            break;
        case T_Agg:
            {
                Agg           *agg = (Agg *) plan;

                sname = "Aggregate";
                switch (agg->aggstrategy)
                {
                    case AGG_PLAIN:
                        pname = "Aggregate";
                        strategy = "Plain";
                        break;
                    case AGG_SORTED:
                        pname = "GroupAggregate";
                        strategy = "Sorted";
                        break;
                    case AGG_HASHED:
                        pname = "HashAggregate";
                        strategy = "Hashed";
                        break;
                    case AGG_MIXED:
                        pname = "MixedAggregate";
                        strategy = "Mixed";
                        break;
                    default:
                        pname = "Aggregate ???";
                        strategy = "???";
                        break;
                }

                if (DO_AGGSPLIT_SKIPFINAL(agg->aggsplit))
                {
                    partialmode = "Partial";
                    pname = psprintf("%s %s", partialmode, pname);
                }
                else if (DO_AGGSPLIT_COMBINE(agg->aggsplit))
                {
                    partialmode = "Finalize";
                    pname = psprintf("%s %s", partialmode, pname);
                }
                else
                    partialmode = "Simple";
            }
            break;
        case T_WindowAgg:
            pname = sname = "WindowAgg";
            break;
        case T_Unique:
            pname = sname = "Unique";
            break;
        case T_SetOp:
            sname = "SetOp";
            switch (((SetOp *) plan)->strategy)
            {
                case SETOP_SORTED:
                    pname = "SetOp";
                    strategy = "Sorted";
                    break;
                case SETOP_HASHED:
                    pname = "HashSetOp";
                    strategy = "Hashed";
                    break;
                default:
                    pname = "SetOp ???";
                    strategy = "???";
                    break;
            }
            break;
        case T_LockRows:
            pname = sname = "LockRows";
            break;
        case T_Limit:
            pname = sname = "Limit";
            break;
        case T_Hash:
            pname = sname = "Hash";
            break;
        default:
            pname = sname = "???";
            break;
    }

    ExplainOpenGroup("Plan",
                     relationship ? NULL : "Plan",
                     true, es);

    if (es->format == EXPLAIN_FORMAT_TEXT)
    {
        if (plan_name)
        {
            appendStringInfoSpaces(es->str, es->indent * 2);
            appendStringInfo(es->str, "%s\n", plan_name);
            es->indent++;
        }
        if (es->indent)
        {
            appendStringInfoSpaces(es->str, es->indent * 2);
            appendStringInfoString(es->str, "->  ");
            es->indent += 2;
        }
        if (plan->parallel_aware)
            appendStringInfoString(es->str, "Parallel ");
        appendStringInfoString(es->str, pname);
        es->indent++;
    }
    else
    {
        ExplainPropertyText("Node Type", sname, es);
        if (strategy)
            ExplainPropertyText("Strategy", strategy, es);
        if (partialmode)
            ExplainPropertyText("Partial Mode", partialmode, es);
        if (operation)
            ExplainPropertyText("Operation", operation, es);
        if (relationship)
            ExplainPropertyText("Parent Relationship", relationship, es);
        if (plan_name)
            ExplainPropertyText("Subplan Name", plan_name, es);
        if (custom_name)
            ExplainPropertyText("Custom Plan Provider", custom_name, es);
        ExplainPropertyBool("Parallel Aware", plan->parallel_aware, es);
    }

    switch (nodeTag(plan))
    {
        case T_SeqScan:
        case T_SampleScan:
        case T_BitmapHeapScan:
        case T_TidScan:
        case T_SubqueryScan:
        case T_FunctionScan:
        case T_TableFuncScan:
        case T_ValuesScan:
        case T_CteScan:
        case T_WorkTableScan:
            ExplainScanTarget((Scan *) plan, es);
            break;
        case T_ForeignScan:
        case T_CustomScan:
            if (((Scan *) plan)->scanrelid > 0)
                ExplainScanTarget((Scan *) plan, es);
            break;
#ifdef PGXC
        case T_RemoteQuery:
            /* Emit node execution list */
            ExplainExecNodes(((RemoteQuery *)plan)->exec_nodes, es);
            break;
#endif
#ifdef XCP
        case T_RemoteSubplan:
            {
                RemoteSubplan  *rsubplan = (RemoteSubplan *) plan;
                List *nodeNameList = NIL;
                ListCell *lc;

                foreach(lc, rsubplan->nodeList)
                {
                    char *nodename = get_pgxc_nodename(
                            PGXCNodeGetNodeOid(lfirst_int(lc),
                                               PGXC_NODE_DATANODE));
                    nodeNameList = lappend(nodeNameList, nodename);
                }

                /* print out destination nodes */
                if (es->format == EXPLAIN_FORMAT_TEXT)
                {
                    if (nodeNameList)
                    {
                        if (es->nodes)
                        {
                            bool             first = true;
                            ListCell        *lc;
#ifdef __OPENTENBASE__
                            int             index = 0;
                            char cursor[NAMEDATALEN];

                            if (explain_query_analyze)
                            {
                                if (rsubplan->cursor)
                                {
                                    if (rsubplan->unique)
										snprintf(cursor, NAMEDATALEN, "%s_"INT64_FORMAT, rsubplan->cursor, rsubplan->unique);
                                    else
                                        strncpy(cursor, rsubplan->cursor, NAMEDATALEN);
                                }
                                else
                                    cursor[0] = '\0';
                            }
#endif
                            foreach(lc, nodeNameList)
                            {
                                char *nodename = (char *) lfirst(lc);
                                if (first)
                                {
                                    appendStringInfo(es->str, " on %s (%s",
                                                     rsubplan->execOnAll ? "all" : "any",
                                                     nodename);
                                    first = false;
                                }
                                else
                                    appendStringInfo(es->str, ",%s", nodename);
#ifdef __OPENTENBASE__
                                if (explain_query_analyze)
                                {
                                    int nodeid = 0;
                                    char *info = NULL;
        
                                    nodeid = list_nth_int(rsubplan->nodeList, index);

                                    info = GetAnalyzeInfo(nodeid, cursor);

                                    if (info)
                                    {
                                        appendStringInfo(es->str, "(%s)", info);
                                        pfree(info);
                                    }

                                    index++;
                                }
#endif
                            }
                            appendStringInfoChar(es->str, ')');
#ifdef __OPENTENBASE__
                            if (explain_query_analyze)
                            {
                                appendStringInfo(es->str, ", (cursor:%s)", cursor);
                            }
#endif
                        }
                        else
                        {
                            appendStringInfo(es->str, " on %s",
                                         rsubplan->execOnAll ? "all" : "any");
                        }
                    }
                    else
                    {
                        appendStringInfo(es->str, " on local node");
                    }
                }
                else
                {
                    ExplainPropertyText("Replicated",
                                        rsubplan->execOnAll ? "no" : "yes",
                                        es);
                    if (es->nodes)
                        ExplainPropertyList("Node List", nodeNameList, es);
                }
            }
            break;
#endif /* XCP */
		case T_IndexScan:
			{
				IndexScan  *indexscan = (IndexScan *) plan;

				ExplainIndexScanDetails(indexscan->indexid,
										indexscan->indexorderdir,
										es);
				ExplainScanTarget((Scan *) indexscan, es);
			}
			break;
		case T_IndexOnlyScan:
			{
				IndexOnlyScan *indexonlyscan = (IndexOnlyScan *) plan;

				ExplainIndexScanDetails(indexonlyscan->indexid,
										indexonlyscan->indexorderdir,
										es);
				ExplainScanTarget((Scan *) indexonlyscan, es);
			}
			break;
		case T_BitmapIndexScan:
			{
				BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan;
				const char *indexname =
				explain_get_index_name(bitmapindexscan->indexid);

				if (es->format == EXPLAIN_FORMAT_TEXT)
					appendStringInfo(es->str, " on %s", indexname);
				else
					ExplainPropertyText("Index Name", indexname, es);
			}
			break;
		case T_ModifyTable:
			ExplainModifyTarget((ModifyTable *) plan, es);
			break;
		case T_NestLoop:
		case T_MergeJoin:
		case T_HashJoin:
			{
				const char *jointype;

				switch (((Join *) plan)->jointype)
				{
					case JOIN_INNER:
						jointype = "Inner";
						break;
					case JOIN_LEFT:
						jointype = "Left";
						break;
					case JOIN_FULL:
						jointype = "Full";
						break;
					case JOIN_RIGHT:
						jointype = "Right";
						break;
					case JOIN_SEMI:
						jointype = "Semi";
						break;
					case JOIN_ANTI:
						jointype = "Anti";
						break;
#ifdef __OPENTENBASE__
					case JOIN_LEFT_SCALAR:
						jointype = "Left Scalar";
						break;
					case JOIN_LEFT_SEMI:
						jointype = "Left Semi";
						break;
#endif
					default:
						jointype = "???";
						break;
				}
				if (es->format == EXPLAIN_FORMAT_TEXT)
				{
					/*
					 * For historical reasons, the join type is interpolated
					 * into the node type name...
					 */
					if (((Join *) plan)->jointype != JOIN_INNER)
						appendStringInfo(es->str, " %s Join", jointype);
					else if (!IsA(plan, NestLoop))
						appendStringInfoString(es->str, " Join");
				}
				else
					ExplainPropertyText("Join Type", jointype, es);
			}
			break;
		case T_SetOp:
			{
				const char *setopcmd;

				switch (((SetOp *) plan)->cmd)
				{
					case SETOPCMD_INTERSECT:
						setopcmd = "Intersect";
						break;
					case SETOPCMD_INTERSECT_ALL:
						setopcmd = "Intersect All";
						break;
					case SETOPCMD_EXCEPT:
						setopcmd = "Except";
						break;
					case SETOPCMD_EXCEPT_ALL:
						setopcmd = "Except All";
						break;
					default:
						setopcmd = "???";
						break;
				}
				if (es->format == EXPLAIN_FORMAT_TEXT)
					appendStringInfo(es->str, " %s", setopcmd);
				else
					ExplainPropertyText("Command", setopcmd, es);
			}
			break;
		default:
			break;
	}

	if (es->costs)
	{
		if (es->format == EXPLAIN_FORMAT_TEXT)
		{
			appendStringInfo(es->str, "  (cost=%.2f..%.2f rows=%.0f width=%d)",
							 plan->startup_cost, plan->total_cost,
							 plan->plan_rows, plan->plan_width);
		}
		else
		{
			ExplainPropertyFloat("Startup Cost", plan->startup_cost, 2, es);
			ExplainPropertyFloat("Total Cost", plan->total_cost, 2, es);
			ExplainPropertyFloat("Plan Rows", plan->plan_rows, 0, es);
			ExplainPropertyInteger("Plan Width", plan->plan_width, es);
		}
	}

	/*
	 * We have to forcibly clean up the instrumentation state because we
	 * haven't done ExecutorEnd yet.  This is pretty grotty ...
	 *
	 * Note: contrib/auto_explain could cause instrumentation to be set up
	 * even though we didn't ask for it here.  Be careful not to print any
	 * instrumentation results the user didn't ask for.  But we do the
	 * InstrEndLoop call anyway, if possible, to reduce the number of cases
	 * auto_explain has to contend with.
	 */
	if (planstate->instrument)
		InstrEndLoop(planstate->instrument);

	if (es->analyze &&
		planstate->instrument && planstate->instrument->nloops > 0)
	{
		double		nloops = planstate->instrument->nloops;
		double		startup_sec = 1000.0 * planstate->instrument->startup / nloops;
		double		total_sec = 1000.0 * planstate->instrument->total / nloops;
		double		rows = planstate->instrument->ntuples / nloops;

		if (es->format == EXPLAIN_FORMAT_TEXT)
		{
			if (es->timing)
				appendStringInfo(es->str,
								 " (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
								 startup_sec, total_sec, rows, nloops);
			else
				appendStringInfo(es->str,
								 " (actual rows=%.0f loops=%.0f)",
								 rows, nloops);
		}
		else
		{
			if (es->timing)
			{
				ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es);
				ExplainPropertyFloat("Actual Total Time", total_sec, 3, es);
			}
			ExplainPropertyFloat("Actual Rows", rows, 0, es);
			ExplainPropertyFloat("Actual Loops", nloops, 0, es);
		}
	}
	else if (es->analyze && planstate->dn_instrument)
	{
		ExplainCommonRemoteInstr(planstate, es);
	}
	else if (es->analyze)
	{
		if (es->format == EXPLAIN_FORMAT_TEXT)
			appendStringInfoString(es->str, " (never executed)");
		else
		{
			if (es->timing)
			{
				ExplainPropertyFloat("Actual Startup Time", 0.0, 3, es);
				ExplainPropertyFloat("Actual Total Time", 0.0, 3, es);
			}
			ExplainPropertyFloat("Actual Rows", 0.0, 0, es);
			ExplainPropertyFloat("Actual Loops", 0.0, 0, es);
		}
	}

	/* in text format, first line ends here */
	if (es->format == EXPLAIN_FORMAT_TEXT)
		appendStringInfoChar(es->str, '\n');

	/* target list */
	if (es->verbose)
		show_plan_tlist(planstate, ancestors, es);

	/* unique join */
	switch (nodeTag(plan))
	{
		case T_NestLoop:
		case T_MergeJoin:
		case T_HashJoin:
			/* try not to be too chatty about this in text mode */
			if (es->format != EXPLAIN_FORMAT_TEXT ||
				(es->verbose && ((Join *) plan)->inner_unique))
				ExplainPropertyBool("Inner Unique",
									((Join *) plan)->inner_unique,
									es);
			break;
		default:
			break;
	}

	/* quals, sort keys, etc */
	switch (nodeTag(plan))
	{
		case T_IndexScan:
			show_scan_qual(((IndexScan *) plan)->indexqualorig,
						   "Index Cond", planstate, ancestors, es);
			if (((IndexScan *) plan)->indexqualorig)
				show_instrumentation_count("Rows Removed by Index Recheck", 2,
										   planstate, es);
			show_scan_qual(((IndexScan *) plan)->indexorderbyorig,
						   "Order By", planstate, ancestors, es);
			show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
			if (plan->qual)
				show_instrumentation_count("Rows Removed by Filter", 1,
										   planstate, es);
			break;
		case T_IndexOnlyScan:
			show_scan_qual(((IndexOnlyScan *) plan)->indexqual,
						   "Index Cond", planstate, ancestors, es);
			if (((IndexOnlyScan *) plan)->indexqual)
				show_instrumentation_count("Rows Removed by Index Recheck", 2,
										   planstate, es);
			show_scan_qual(((IndexOnlyScan *) plan)->indexorderby,
						   "Order By", planstate, ancestors, es);
			show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
			if (plan->qual)
				show_instrumentation_count("Rows Removed by Filter", 1,
										   planstate, es);
			if (es->analyze)
				ExplainPropertyLong("Heap Fetches",
									((IndexOnlyScanState *) planstate)->ioss_HeapFetches, es);
			break;
		case T_BitmapIndexScan:
			show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
						   "Index Cond", planstate, ancestors, es);
			break;
#ifdef PGXC
        case T_RemoteQuery:
            /* Remote query */
            ExplainRemoteQuery((RemoteQuery *)plan, planstate, ancestors, es);
            Assert(!plan->qual);
            break;
#endif
#ifdef XCP
        case T_RemoteSubplan:
            {
                RemoteSubplan  *rsubplan = (RemoteSubplan *) plan;

                /* print out destination nodes */
                if (es->format == EXPLAIN_FORMAT_TEXT)
                {
                    if (list_length(rsubplan->distributionNodes) > 0)
                    {
                        char         label[24];
                        AttrNumber     dkey = rsubplan->distributionKey;
                        snprintf(label,24, "Distribute results by %c",
                                rsubplan->distributionType);
                        if (dkey == InvalidAttrNumber)
                        {
                            appendStringInfoSpaces(es->str, es->indent * 2);
                            appendStringInfo(es->str, "%s\n", label);
                        }
                        else
                        {
                            TargetEntry *tle = NULL;
                            if (plan->targetlist)
                                tle = (TargetEntry *) list_nth(plan->targetlist,
                                                               dkey-1);
                            if (IsA(tle, TargetEntry))
                                show_expression((Node *) tle->expr, label,
                                                planstate, ancestors,
                                                false, es);
                        }
                    }
                }

                /* add info about output sort order */
                if (es->verbose)
                    show_simple_sort_keys((RemoteSubplanState *)planstate,
                                          ancestors, es);
            }
            break;
#endif
        case T_BitmapHeapScan:
            show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
                           "Recheck Cond", planstate, ancestors, es);
            if (((BitmapHeapScan *) plan)->bitmapqualorig)
                show_instrumentation_count("Rows Removed by Index Recheck", 2,
                                           planstate, es);
            show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
            if (plan->qual)
                show_instrumentation_count("Rows Removed by Filter", 1,
                                           planstate, es);
            if (es->analyze)
                show_tidbitmap_info((BitmapHeapScanState *) planstate, es);
            break;
        case T_SampleScan:
            show_tablesample(((SampleScan *) plan)->tablesample,
                             planstate, ancestors, es);
            /* FALL THRU to print additional fields the same as SeqScan */
        case T_SeqScan:
        case T_ValuesScan:
        case T_CteScan:
        case T_NamedTuplestoreScan:
        case T_WorkTableScan:
        case T_SubqueryScan:
            show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
            if (plan->qual)
                show_instrumentation_count("Rows Removed by Filter", 1,
                                           planstate, es);
            break;
        case T_Gather:
            {
                Gather       *gather = (Gather *) plan;

                show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
                if (plan->qual)
                    show_instrumentation_count("Rows Removed by Filter", 1,
                                               planstate, es);
                ExplainPropertyInteger("Workers Planned",
                                       gather->num_workers, es);
                if (es->analyze)
                {
                    int            nworkers;

                    nworkers = ((GatherState *) planstate)->nworkers_launched;
                    ExplainPropertyInteger("Workers Launched",
                                           nworkers, es);
                }
                if (gather->single_copy || es->format != EXPLAIN_FORMAT_TEXT)
                    ExplainPropertyBool("Single Copy", gather->single_copy, es);
            }
            break;
        case T_GatherMerge:
            {
                GatherMerge *gm = (GatherMerge *) plan;

                show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
                if (plan->qual)
                    show_instrumentation_count("Rows Removed by Filter", 1,
                                               planstate, es);
                ExplainPropertyInteger("Workers Planned",
                                       gm->num_workers, es);
                if (es->analyze)
                {
                    int            nworkers;

                    nworkers = ((GatherMergeState *) planstate)->nworkers_launched;
                    ExplainPropertyInteger("Workers Launched",
                                           nworkers, es);
                }
            }
            break;
        case T_FunctionScan:
            if (es->verbose)
            {
                List       *fexprs = NIL;
                ListCell   *lc;

                foreach(lc, ((FunctionScan *) plan)->functions)
                {
                    RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);

                    fexprs = lappend(fexprs, rtfunc->funcexpr);
                }
                /* We rely on show_expression to insert commas as needed */
                show_expression((Node *) fexprs,
                                "Function Call", planstate, ancestors,
                                es->verbose, es);
            }
            show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
            if (plan->qual)
                show_instrumentation_count("Rows Removed by Filter", 1,
                                           planstate, es);
            break;
        case T_TableFuncScan:
            if (es->verbose)
            {
                TableFunc  *tablefunc = ((TableFuncScan *) plan)->tablefunc;

                show_expression((Node *) tablefunc,
                                "Table Function Call", planstate, ancestors,
                                es->verbose, es);
            }
            show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
            if (plan->qual)
                show_instrumentation_count("Rows Removed by Filter", 1,
                                           planstate, es);
            break;
        case T_TidScan:
            {
                /*
                 * The tidquals list has OR semantics, so be sure to show it
                 * as an OR condition.
                 */
                List       *tidquals = ((TidScan *) plan)->tidquals;

                if (list_length(tidquals) > 1)
                    tidquals = list_make1(make_orclause(tidquals));
                show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es);
                show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
                if (plan->qual)
                    show_instrumentation_count("Rows Removed by Filter", 1,
                                               planstate, es);
            }
            break;
        case T_ForeignScan:
            show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
            if (plan->qual)
                show_instrumentation_count("Rows Removed by Filter", 1,
                                           planstate, es);
            show_foreignscan_info((ForeignScanState *) planstate, es);
            break;
        case T_CustomScan:
            {
                CustomScanState *css = (CustomScanState *) planstate;

                show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
                if (plan->qual)
                    show_instrumentation_count("Rows Removed by Filter", 1,
                                               planstate, es);
                if (css->methods->ExplainCustomScan)
                    css->methods->ExplainCustomScan(css, ancestors, es);
            }
            break;
        case T_NestLoop:
            show_upper_qual(((NestLoop *) plan)->join.joinqual,
                            "Join Filter", planstate, ancestors, es);
            if (((NestLoop *) plan)->join.joinqual)
                show_instrumentation_count("Rows Removed by Join Filter", 1,
                                           planstate, es);
            show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
            if (plan->qual)
                show_instrumentation_count("Rows Removed by Filter", 2,
                                           planstate, es);
            break;
        case T_MergeJoin:
            show_upper_qual(((MergeJoin *) plan)->mergeclauses,
                            "Merge Cond", planstate, ancestors, es);
            show_upper_qual(((MergeJoin *) plan)->join.joinqual,
                            "Join Filter", planstate, ancestors, es);
            if (((MergeJoin *) plan)->join.joinqual)
                show_instrumentation_count("Rows Removed by Join Filter", 1,
                                           planstate, es);
            show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
            if (plan->qual)
                show_instrumentation_count("Rows Removed by Filter", 2,
                                           planstate, es);
            break;
        case T_HashJoin:
            show_upper_qual(((HashJoin *) plan)->hashclauses,
                            "Hash Cond", planstate, ancestors, es);
            show_upper_qual(((HashJoin *) plan)->join.joinqual,
                            "Join Filter", planstate, ancestors, es);
            if (((HashJoin *) plan)->join.joinqual)
                show_instrumentation_count("Rows Removed by Join Filter", 1,
                                           planstate, es);
            show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
            if (plan->qual)
                show_instrumentation_count("Rows Removed by Filter", 2,
                                           planstate, es);
            break;
        case T_Agg:
            show_agg_keys(castNode(AggState, planstate), ancestors, es);
            show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
            if (plan->qual)
                show_instrumentation_count("Rows Removed by Filter", 1,
                                           planstate, es);
            break;
        case T_Group:
            show_group_keys(castNode(GroupState, planstate), ancestors, es);
            show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
            if (plan->qual)
                show_instrumentation_count("Rows Removed by Filter", 1,
                                           planstate, es);
            break;
        case T_Sort:
            show_sort_keys(castNode(SortState, planstate), ancestors, es);
            show_sort_info(castNode(SortState, planstate), es);
            break;
        case T_MergeAppend:
            show_merge_append_keys(castNode(MergeAppendState, planstate),
                                   ancestors, es);
#ifdef __OPENTENBASE__
            show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
#endif

            break;
        case T_Result:
            show_upper_qual((List *) ((Result *) plan)->resconstantqual,
                            "One-Time Filter", planstate, ancestors, es);
            show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
            if (plan->qual)
                show_instrumentation_count("Rows Removed by Filter", 1,
                                           planstate, es);
            break;
        case T_ModifyTable:
            show_modifytable_info(castNode(ModifyTableState, planstate), ancestors,
                                  es);
            break;
        case T_Hash:
            show_hash_info(castNode(HashState, planstate), es);
            break;
        default:
            break;
    }

    /* Show buffer usage */
    if (es->buffers && planstate->instrument)
        show_buffer_usage(es, &planstate->instrument->bufusage);

    /* Show worker detail */
    if (es->analyze && es->verbose && planstate->worker_instrument)
    {
        WorkerInstrumentation *w = planstate->worker_instrument;
        bool        opened_group = false;
        int            n;

        for (n = 0; n < w->num_workers; ++n)
        {
            Instrumentation *instrument = &w->instrument[n];
            double        nloops = instrument->nloops;
            double        startup_sec;
            double        total_sec;
            double        rows;

            if (nloops <= 0)
                continue;
            startup_sec = 1000.0 * instrument->startup / nloops;
            total_sec = 1000.0 * instrument->total / nloops;
            rows = instrument->ntuples / nloops;

            if (es->format == EXPLAIN_FORMAT_TEXT)
            {
                appendStringInfoSpaces(es->str, es->indent * 2);
                appendStringInfo(es->str, "Worker %d: ", n);
                if (es->timing)
                    appendStringInfo(es->str,
                                     "actual time=%.3f..%.3f rows=%.0f loops=%.0f\n",
                                     startup_sec, total_sec, rows, nloops);
                else
                    appendStringInfo(es->str,
                                     "actual rows=%.0f loops=%.0f\n",
                                     rows, nloops);
                es->indent++;
                if (es->buffers)
                    show_buffer_usage(es, &instrument->bufusage);
                es->indent--;
            }
            else
            {
                if (!opened_group)
                {
                    ExplainOpenGroup("Workers", "Workers", false, es);
                    opened_group = true;
                }
                ExplainOpenGroup("Worker", NULL, true, es);
                ExplainPropertyInteger("Worker Number", n, es);

                if (es->timing)
                {
                    ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es);
                    ExplainPropertyFloat("Actual Total Time", total_sec, 3, es);
                }
                ExplainPropertyFloat("Actual Rows", rows, 0, es);
                ExplainPropertyFloat("Actual Loops", nloops, 0, es);

                if (es->buffers)
                    show_buffer_usage(es, &instrument->bufusage);

                ExplainCloseGroup("Worker", NULL, true, es);
            }
        }

        if (opened_group)
            ExplainCloseGroup("Workers", "Workers", false, es);
    }

    /* Get ready to display the child plans */
    haschildren = planstate->initPlan ||
        outerPlanState(planstate) ||
        innerPlanState(planstate) ||
        IsA(plan, ModifyTable) ||
        IsA(plan, Append) ||
        IsA(plan, MergeAppend) ||
        IsA(plan, BitmapAnd) ||
        IsA(plan, BitmapOr) ||
        IsA(plan, SubqueryScan) ||
        (IsA(planstate, CustomScanState) &&
         ((CustomScanState *) planstate)->custom_ps != NIL) ||
        planstate->subPlan;
    if (haschildren)
    {
        ExplainOpenGroup("Plans", "Plans", false, es);
        /* Pass current PlanState as head of ancestors list for children */
        ancestors = lcons(planstate, ancestors);
    }

    /* initPlan-s */
    if (planstate->initPlan)
        ExplainSubPlans(planstate->initPlan, ancestors, "InitPlan", es);

    /* lefttree */
    if (outerPlanState(planstate))
        ExplainNode(outerPlanState(planstate), ancestors,
                    "Outer", NULL, es);

    /* righttree */
    if (innerPlanState(planstate))
        ExplainNode(innerPlanState(planstate), ancestors,
                    "Inner", NULL, es);

    /* special child plans */
    switch (nodeTag(plan))
    {
        case T_ModifyTable:
#ifdef __OPENTENBASE__
			/* compatible with make_modifytable */
			if (((ModifyTable *) plan)->haspartparent &&
			    (((ModifyTable *) plan)->operation == CMD_UPDATE ||
			     ((ModifyTable *) plan)->operation == CMD_DELETE))
            {
                ExplainMemberNodes(((ModifyTable *) plan)->partplans,
                               ((ModifyTableState *) planstate)->partplans,
                               ancestors, es);
            }
            else
#endif
            ExplainMemberNodes(((ModifyTable *) plan)->plans,
                               ((ModifyTableState *) planstate)->mt_plans,
                               ancestors, es);
            break;
        case T_Append:
            ExplainMemberNodes(((Append *) plan)->appendplans,
                               ((AppendState *) planstate)->appendplans,
                               ancestors, es);
            break;
        case T_MergeAppend:
            ExplainMemberNodes(((MergeAppend *) plan)->mergeplans,
                               ((MergeAppendState *) planstate)->mergeplans,
                               ancestors, es);
            break;
        case T_BitmapAnd:
            ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
                               ((BitmapAndState *) planstate)->bitmapplans,
                               ancestors, es);
            break;
        case T_BitmapOr:
            ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
                               ((BitmapOrState *) planstate)->bitmapplans,
                               ancestors, es);
            break;
        case T_SubqueryScan:
            ExplainNode(((SubqueryScanState *) planstate)->subplan, ancestors,
                        "Subquery", NULL, es);
            break;
        case T_CustomScan:
            ExplainCustomChildren((CustomScanState *) planstate,
                                  ancestors, es);
            break;
        default:
            break;
    }

    /* subPlan-s */
    if (planstate->subPlan)
        ExplainSubPlans(planstate->subPlan, ancestors, "SubPlan", es);

    /* end of child plans */
    if (haschildren)
    {
        ancestors = list_delete_first(ancestors);
        ExplainCloseGroup("Plans", "Plans", false, es);
    }

    /* in text format, undo whatever indentation we added */
    if (es->format == EXPLAIN_FORMAT_TEXT)
        es->indent = save_indent;

    ExplainCloseGroup("Plan",
                      relationship ? NULL : "Plan",
                      true, es);
}

/*
 * Show the targetlist of a plan node
 */
static void
show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es)
{
    Plan       *plan = planstate->plan;
    List       *context;
    List       *result = NIL;
    bool        useprefix;
    ListCell   *lc;

    /* No work if empty tlist (this occurs eg in bitmap indexscans) */
    if (plan->targetlist == NIL)
        return;
    /* The tlist of an Append isn't real helpful, so suppress it */
    if (IsA(plan, Append))
        return;
    /* Likewise for MergeAppend and RecursiveUnion */
    if (IsA(plan, MergeAppend))
        return;
    if (IsA(plan, RecursiveUnion))
        return;

    /*
     * Likewise for ForeignScan that executes a direct INSERT/UPDATE/DELETE
     *
     * Note: the tlist for a ForeignScan that executes a direct INSERT/UPDATE
     * might contain subplan output expressions that are confusing in this
     * context.  The tlist for a ForeignScan that executes a direct UPDATE/
     * DELETE always contains "junk" target columns to identify the exact row
     * to update or delete, which would be confusing in this context.  So, we
     * suppress it in all the cases.
     */
    if (IsA(plan, ForeignScan) &&
        ((ForeignScan *) plan)->operation != CMD_SELECT)
        return;

    /* Set up deparsing context */
    context = set_deparse_context_planstate(es->deparse_cxt,
                                            (Node *) planstate,
                                            ancestors);
    useprefix = list_length(es->rtable) > 1;

    /* Deparse each result column (we now include resjunk ones) */
    foreach(lc, plan->targetlist)
    {
        TargetEntry *tle = (TargetEntry *) lfirst(lc);

        result = lappend(result,
                         deparse_expression((Node *) tle->expr, context,
                                            useprefix, false));
    }

    /* Print results */
    ExplainPropertyList("Output", result, es);
}

/*
 * Show a generic expression
 */
static void
show_expression(Node *node, const char *qlabel,
                PlanState *planstate, List *ancestors,
                bool useprefix, ExplainState *es)
{
    List       *context;
    char       *exprstr;

    /* Set up deparsing context */
    context = set_deparse_context_planstate(es->deparse_cxt,
                                            (Node *) planstate,
                                            ancestors);

    /* Deparse the expression */
    exprstr = deparse_expression(node, context, useprefix, false);

    /* And add to es->str */
    ExplainPropertyText(qlabel, exprstr, es);
}

/*
 * Show a qualifier expression (which is a List with implicit AND semantics)
 */
static void
show_qual(List *qual, const char *qlabel,
          PlanState *planstate, List *ancestors,
          bool useprefix, ExplainState *es)
{
    Node       *node;

    /* No work if empty qual */
    if (qual == NIL)
        return;

    /* Convert AND list to explicit AND */
    node = (Node *) make_ands_explicit(qual);

    /* And show it */
    show_expression(node, qlabel, planstate, ancestors, useprefix, es);
}

/*
 * Show a qualifier expression for a scan plan node
 */
static void
show_scan_qual(List *qual, const char *qlabel,
               PlanState *planstate, List *ancestors,
               ExplainState *es)
{
    bool        useprefix;

    useprefix = (IsA(planstate->plan, SubqueryScan) ||es->verbose);
    show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
}

/*
 * Show a qualifier expression for an upper-level plan node
 */
static void
show_upper_qual(List *qual, const char *qlabel,
                PlanState *planstate, List *ancestors,
                ExplainState *es)
{
    bool        useprefix;

    useprefix = (list_length(es->rtable) > 1 || es->verbose);
    show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
}

/*
 * Show the sort keys for a Sort node.
 */
static void
show_sort_keys(SortState *sortstate, List *ancestors, ExplainState *es)
{
    Sort       *plan = (Sort *) sortstate->ss.ps.plan;

    show_sort_group_keys((PlanState *) sortstate, "Sort Key",
                         plan->numCols, plan->sortColIdx,
                         plan->sortOperators, plan->collations,
                         plan->nullsFirst,
                         ancestors, es);
}

/*
 * Show the sort keys for a SimpleSort node.
 */
static void
show_simple_sort_keys(RemoteSubplanState *planstate, List *ancestors, ExplainState *es)
{
    Plan           *plan = ((ScanState *)planstate)->ps.plan;
    RemoteSubplan  *remoteplan = (RemoteSubplan *)plan;

    SimpleSort *sort = (SimpleSort *)remoteplan->sort;

    /* if remote subplan does not sort the results */
    if (!sort)
        return;

    show_sort_group_keys((PlanState *) planstate, "Sort Key",
                         sort->numCols, sort->sortColIdx,
                         sort->sortOperators, sort->sortCollations,
                         sort->nullsFirst,
                         ancestors, es);
}

/*
 * Likewise, for a MergeAppend node.
 */
static void
show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
                       ExplainState *es)
{
    MergeAppend *plan = (MergeAppend *) mstate->ps.plan;

    show_sort_group_keys((PlanState *) mstate, "Sort Key",
                         plan->numCols, plan->sortColIdx,
                         plan->sortOperators, plan->collations,
                         plan->nullsFirst,
                         ancestors, es);
}

/*
 * Show the grouping keys for an Agg node.
 */
static void
show_agg_keys(AggState *astate, List *ancestors,
              ExplainState *es)
{
    Agg           *plan = (Agg *) astate->ss.ps.plan;

    if (plan->numCols > 0 || plan->groupingSets)
    {
        /* The key columns refer to the tlist of the child plan */
        ancestors = lcons(astate, ancestors);

        if (plan->groupingSets)
            show_grouping_sets(outerPlanState(astate), plan, ancestors, es);
        else
            show_sort_group_keys(outerPlanState(astate), "Group Key",
                                 plan->numCols, plan->grpColIdx,
                                 NULL, NULL, NULL,
                                 ancestors, es);

        ancestors = list_delete_first(ancestors);
    }
}

static void
show_grouping_sets(PlanState *planstate, Agg *agg,
                   List *ancestors, ExplainState *es)
{
    List       *context;
    bool        useprefix;
    ListCell   *lc;

    /* Set up deparsing context */
    context = set_deparse_context_planstate(es->deparse_cxt,
                                            (Node *) planstate,
                                            ancestors);
    useprefix = (list_length(es->rtable) > 1 || es->verbose);

    ExplainOpenGroup("Grouping Sets", "Grouping Sets", false, es);

    show_grouping_set_keys(planstate, agg, NULL,
                           context, useprefix, ancestors, es);

    foreach(lc, agg->chain)
    {
        Agg           *aggnode = lfirst(lc);
        Sort       *sortnode = (Sort *) aggnode->plan.lefttree;

        show_grouping_set_keys(planstate, aggnode, sortnode,
                               context, useprefix, ancestors, es);
    }

    ExplainCloseGroup("Grouping Sets", "Grouping Sets", false, es);
}

static void
show_grouping_set_keys(PlanState *planstate,
                       Agg *aggnode, Sort *sortnode,
                       List *context, bool useprefix,
                       List *ancestors, ExplainState *es)
{// #lizard forgives
    Plan       *plan = planstate->plan;
    char       *exprstr;
    ListCell   *lc;
    List       *gsets = aggnode->groupingSets;
    AttrNumber *keycols = aggnode->grpColIdx;
    const char *keyname;
    const char *keysetname;

    if (aggnode->aggstrategy == AGG_HASHED || aggnode->aggstrategy == AGG_MIXED)
    {
        keyname = "Hash Key";
        keysetname = "Hash Keys";
    }
    else
    {
        keyname = "Group Key";
        keysetname = "Group Keys";
    }

    ExplainOpenGroup("Grouping Set", NULL, true, es);

    if (sortnode)
    {
        show_sort_group_keys(planstate, "Sort Key",
                             sortnode->numCols, sortnode->sortColIdx,
                             sortnode->sortOperators, sortnode->collations,
                             sortnode->nullsFirst,
                             ancestors, es);
        if (es->format == EXPLAIN_FORMAT_TEXT)
            es->indent++;
    }

    ExplainOpenGroup(keysetname, keysetname, false, es);

    foreach(lc, gsets)
    {
        List       *result = NIL;
        ListCell   *lc2;

        foreach(lc2, (List *) lfirst(lc))
        {
            Index        i = lfirst_int(lc2);
            AttrNumber    keyresno = keycols[i];
            TargetEntry *target = get_tle_by_resno(plan->targetlist,
                                                   keyresno);

            if (!target)
                elog(ERROR, "no tlist entry for key %d", keyresno);
            /* Deparse the expression, showing any top-level cast */
            exprstr = deparse_expression((Node *) target->expr, context,
                                         useprefix, true);

            result = lappend(result, exprstr);
        }

        if (!result && es->format == EXPLAIN_FORMAT_TEXT)
            ExplainPropertyText(keyname, "()", es);
        else
            ExplainPropertyListNested(keyname, result, es);
    }

    ExplainCloseGroup(keysetname, keysetname, false, es);

    if (sortnode && es->format == EXPLAIN_FORMAT_TEXT)
        es->indent--;

    ExplainCloseGroup("Grouping Set", NULL, true, es);
}

/*
 * Show the grouping keys for a Group node.
 */
static void
show_group_keys(GroupState *gstate, List *ancestors,
                ExplainState *es)
{
    Group       *plan = (Group *) gstate->ss.ps.plan;

    /* The key columns refer to the tlist of the child plan */
    ancestors = lcons(gstate, ancestors);
    show_sort_group_keys(outerPlanState(gstate), "Group Key",
                         plan->numCols, plan->grpColIdx,
                         NULL, NULL, NULL,
                         ancestors, es);
    ancestors = list_delete_first(ancestors);
}

/*
 * Common code to show sort/group keys, which are represented in plan nodes
 * as arrays of targetlist indexes.  If it's a sort key rather than a group
 * key, also pass sort operators/collations/nullsFirst arrays.
 */
static void
show_sort_group_keys(PlanState *planstate, const char *qlabel,
                     int nkeys, AttrNumber *keycols,
                     Oid *sortOperators, Oid *collations, bool *nullsFirst,
                     List *ancestors, ExplainState *es)
{
    Plan       *plan = planstate->plan;
    List       *context;
    List       *result = NIL;
    StringInfoData sortkeybuf;
    bool        useprefix;
    int            keyno;

    if (nkeys <= 0)
        return;

    initStringInfo(&sortkeybuf);

    /* Set up deparsing context */
    context = set_deparse_context_planstate(es->deparse_cxt,
                                            (Node *) planstate,
                                            ancestors);
    useprefix = (list_length(es->rtable) > 1 || es->verbose);

    for (keyno = 0; keyno < nkeys; keyno++)
    {
        /* find key expression in tlist */
        AttrNumber    keyresno = keycols[keyno];
        TargetEntry *target = get_tle_by_resno(plan->targetlist,
                                               keyresno);
        char       *exprstr;

        if (!target)
            elog(ERROR, "no tlist entry for key %d", keyresno);
        /* Deparse the expression, showing any top-level cast */
        exprstr = deparse_expression((Node *) target->expr, context,
                                     useprefix, true);
        resetStringInfo(&sortkeybuf);
        appendStringInfoString(&sortkeybuf, exprstr);
        /* Append sort order information, if relevant */
        if (sortOperators != NULL)
            show_sortorder_options(&sortkeybuf,
                                   (Node *) target->expr,
                                   sortOperators[keyno],
                                   collations[keyno],
                                   nullsFirst[keyno]);
        /* Emit one property-list item per sort key */
        result = lappend(result, pstrdup(sortkeybuf.data));
    }

    ExplainPropertyList(qlabel, result, es);
}

/*
 * Append nondefault characteristics of the sort ordering of a column to buf
 * (collation, direction, NULLS FIRST/LAST)
 */
static void
show_sortorder_options(StringInfo buf, Node *sortexpr,
                       Oid sortOperator, Oid collation, bool nullsFirst)
{// #lizard forgives
    Oid            sortcoltype = exprType(sortexpr);
    bool        reverse = false;
    TypeCacheEntry *typentry;

    typentry = lookup_type_cache(sortcoltype,
                                 TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);

    /*
     * Print COLLATE if it's not default.  There are some cases where this is
     * redundant, eg if expression is a column whose declared collation is
     * that collation, but it's hard to distinguish that here.
     */
    if (OidIsValid(collation) && collation != DEFAULT_COLLATION_OID)
    {
        char       *collname = get_collation_name(collation);

        if (collname == NULL)
            elog(ERROR, "cache lookup failed for collation %u", collation);
        appendStringInfo(buf, " COLLATE %s", quote_identifier(collname));
    }

    /* Print direction if not ASC, or USING if non-default sort operator */
    if (sortOperator == typentry->gt_opr)
    {
        appendStringInfoString(buf, " DESC");
        reverse = true;
    }
    else if (sortOperator != typentry->lt_opr)
    {
        char       *opname = get_opname(sortOperator);

        if (opname == NULL)
            elog(ERROR, "cache lookup failed for operator %u", sortOperator);
        appendStringInfo(buf, " USING %s", opname);
        /* Determine whether operator would be considered ASC or DESC */
        (void) get_equality_op_for_ordering_op(sortOperator, &reverse);
    }

    /* Add NULLS FIRST/LAST only if it wouldn't be default */
    if (nullsFirst && !reverse)
    {
        appendStringInfoString(buf, " NULLS FIRST");
    }
    else if (!nullsFirst && reverse)
    {
        appendStringInfoString(buf, " NULLS LAST");
    }
}

/*
 * Show TABLESAMPLE properties
 */
static void
show_tablesample(TableSampleClause *tsc, PlanState *planstate,
                 List *ancestors, ExplainState *es)
{
    List       *context;
    bool        useprefix;
    char       *method_name;
    List       *params = NIL;
    char       *repeatable;
    ListCell   *lc;

    /* Set up deparsing context */
    context = set_deparse_context_planstate(es->deparse_cxt,
                                            (Node *) planstate,
                                            ancestors);
    useprefix = list_length(es->rtable) > 1;

    /* Get the tablesample method name */
    method_name = get_func_name(tsc->tsmhandler);

    /* Deparse parameter expressions */
    foreach(lc, tsc->args)
    {
        Node       *arg = (Node *) lfirst(lc);

        params = lappend(params,
                         deparse_expression(arg, context,
                                            useprefix, false));
    }
    if (tsc->repeatable)
        repeatable = deparse_expression((Node *) tsc->repeatable, context,
                                        useprefix, false);
    else
        repeatable = NULL;

    /* Print results */
    if (es->format == EXPLAIN_FORMAT_TEXT)
    {
        bool        first = true;

        appendStringInfoSpaces(es->str, es->indent * 2);
        appendStringInfo(es->str, "Sampling: %s (", method_name);
        foreach(lc, params)
        {
            if (!first)
                appendStringInfoString(es->str, ", ");
            appendStringInfoString(es->str, (const char *) lfirst(lc));
            first = false;
        }
        appendStringInfoChar(es->str, ')');
        if (repeatable)
            appendStringInfo(es->str, " REPEATABLE (%s)", repeatable);
        appendStringInfoChar(es->str, '\n');
    }
    else
    {
        ExplainPropertyText("Sampling Method", method_name, es);
        ExplainPropertyList("Sampling Parameters", params, es);
        if (repeatable)
            ExplainPropertyText("Repeatable Seed", repeatable, es);
    }
}

/*
 * If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
 */
static void
show_sort_info(SortState *sortstate, ExplainState *es)
{
	if (!es->analyze)
		return;

	if (sortstate->sort_Done && sortstate->tuplesortstate != NULL)
    {
        Tuplesortstate *state = (Tuplesortstate *) sortstate->tuplesortstate;
		TuplesortInstrumentation stats;
        const char *sortMethod;
        const char *spaceType;
        long        spaceUsed;

		tuplesort_get_stats(state, &stats);
		sortMethod = tuplesort_method_name(stats.sortMethod);
		spaceType = tuplesort_space_type_name(stats.spaceType);
		spaceUsed = stats.spaceUsed;

        if (es->format == EXPLAIN_FORMAT_TEXT)
        {
            appendStringInfoSpaces(es->str, es->indent * 2);
            appendStringInfo(es->str, "Sort Method: %s  %s: %ldkB\n",
                             sortMethod, spaceType, spaceUsed);
        }
        else
        {
            ExplainPropertyText("Sort Method", sortMethod, es);
            ExplainPropertyLong("Sort Space Used", spaceUsed, es);
            ExplainPropertyText("Sort Space Type", spaceType, es);
        }
    }

	if (sortstate->shared_info != NULL)
	{
		int			n;
		bool		opened_group = false;

		for (n = 0; n < sortstate->shared_info->num_workers; n++)
		{
			TuplesortInstrumentation *sinstrument;
			const char *sortMethod;
			const char *spaceType;
			long		spaceUsed;

			sinstrument = &sortstate->shared_info->sinstrument[n];
			if (sinstrument->sortMethod == SORT_TYPE_STILL_IN_PROGRESS)
				continue;		/* ignore any unfilled slots */
			sortMethod = tuplesort_method_name(sinstrument->sortMethod);
			spaceType = tuplesort_space_type_name(sinstrument->spaceType);
			spaceUsed = sinstrument->spaceUsed;

			if (es->format == EXPLAIN_FORMAT_TEXT)
			{
				appendStringInfoSpaces(es->str, es->indent * 2);
				appendStringInfo(es->str,
								 "Worker %d:  Sort Method: %s  %s: %ldkB\n",
								 n, sortMethod, spaceType, spaceUsed);
			}
			else
			{
				if (!opened_group)
				{
					ExplainOpenGroup("Workers", "Workers", false, es);
					opened_group = true;
				}
				ExplainOpenGroup("Worker", NULL, true, es);
				ExplainPropertyInteger("Worker Number", n, es);
				ExplainPropertyText("Sort Method", sortMethod, es);
				ExplainPropertyLong("Sort Space Used", spaceUsed, es);
				ExplainPropertyText("Sort Space Type", spaceType, es);
				ExplainCloseGroup("Worker", NULL, true, es);
			}
		}
		if (opened_group)
			ExplainCloseGroup("Workers", "Workers", false, es);
	}
#ifdef __OPENTENBASE__
	else if (sortstate->instrument.spaceType != -1)
	{
		/* try our cached distributed instrument */
		/* same logic above */
		const char *sortMethod = tuplesort_method_name(sortstate->instrument.sortMethod);
		const char *spaceType = tuplesort_space_type_name(sortstate->instrument.spaceType);
		long        spaceUsed = sortstate->instrument.spaceUsed;
		
		/* -1 means invalid value, indicate that this node executed by ourself */
		Assert(sortstate->instrument.sortMethod != -1);
		
		if (es->format == EXPLAIN_FORMAT_TEXT)
		{
			appendStringInfoSpaces(es->str, es->indent * 2);
			appendStringInfo(es->str, "Sort Method: %s  %s: %ldkB\n",
			                 sortMethod, spaceType, spaceUsed);
		}
		else
		{
			ExplainPropertyText("Sort Method", sortMethod, es);
			ExplainPropertyLong("Sort Space Used", spaceUsed, es);
			ExplainPropertyText("Sort Space Type", spaceType, es);
		}
	}
#endif
}

/*
 * Show information on hash buckets/batches.
 */
static void
show_hash_info(HashState *hashstate, ExplainState *es)
{
	HashInstrumentation *hinstrument = NULL;

	/*
	 * In a parallel query, the leader process may or may not have run the
	 * hash join, and even if it did it may not have built a hash table due to
	 * timing (if it started late it might have seen no tuples in the outer
	 * relation and skipped building the hash table).  Therefore we have to be
	 * prepared to get instrumentation data from a worker if there is no hash
	 * table.
	 */
	if (hashstate->hashtable)
	{
		hinstrument = (HashInstrumentation *)
			palloc(sizeof(HashInstrumentation));
		ExecHashGetInstrumentation(hinstrument, hashstate->hashtable);
	}
	else if (hashstate->shared_info)
	{
		SharedHashInfo *shared_info = hashstate->shared_info;
		int		i;

		/* Find the first worker that built a hash table. */
		for (i = 0; i < shared_info->num_workers; ++i)
		{
			if (shared_info->hinstrument[i].nbatch > 0)
			{
				hinstrument = &shared_info->hinstrument[i];
				break;
			}
		}
	}

	if (hinstrument)
    {
		long		spacePeakKb = (hinstrument->space_peak + 1023) / 1024;

        if (es->format != EXPLAIN_FORMAT_TEXT)
        {
			ExplainPropertyLong("Hash Buckets", hinstrument->nbuckets, es);
            ExplainPropertyLong("Original Hash Buckets",
								hinstrument->nbuckets_original, es);
			ExplainPropertyLong("Hash Batches", hinstrument->nbatch, es);
            ExplainPropertyLong("Original Hash Batches",
								hinstrument->nbatch_original, es);
            ExplainPropertyLong("Peak Memory Usage", spacePeakKb, es);
        }
		else if (hinstrument->nbatch_original != hinstrument->nbatch ||
				 hinstrument->nbuckets_original != hinstrument->nbuckets)
        {
            appendStringInfoSpaces(es->str, es->indent * 2);
            appendStringInfo(es->str,
                             "Buckets: %d (originally %d)  Batches: %d (originally %d)  Memory Usage: %ldkB\n",
							 hinstrument->nbuckets,
							 hinstrument->nbuckets_original,
							 hinstrument->nbatch,
							 hinstrument->nbatch_original,
                             spacePeakKb);
        }
        else
        {
            appendStringInfoSpaces(es->str, es->indent * 2);
            appendStringInfo(es->str,
                             "Buckets: %d  Batches: %d  Memory Usage: %ldkB\n",
							 hinstrument->nbuckets, hinstrument->nbatch,
                             spacePeakKb);
        }
    }
}

/*
 * If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
 */
static void
show_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es)
{
    if (es->format != EXPLAIN_FORMAT_TEXT)
    {
        ExplainPropertyLong("Exact Heap Blocks", planstate->exact_pages, es);
        ExplainPropertyLong("Lossy Heap Blocks", planstate->lossy_pages, es);
    }
    else
    {
        if (planstate->exact_pages > 0 || planstate->lossy_pages > 0)
        {
            appendStringInfoSpaces(es->str, es->indent * 2);
            appendStringInfoString(es->str, "Heap Blocks:");
            if (planstate->exact_pages > 0)
                appendStringInfo(es->str, " exact=%ld", planstate->exact_pages);
            if (planstate->lossy_pages > 0)
                appendStringInfo(es->str, " lossy=%ld", planstate->lossy_pages);
            appendStringInfoChar(es->str, '\n');
        }
    }
}

/*
 * If it's EXPLAIN ANALYZE, show instrumentation information for a plan node
 *
 * "which" identifies which instrumentation counter to print
 */
static void
show_instrumentation_count(const char *qlabel, int which,
                           PlanState *planstate, ExplainState *es)
{
    double        nfiltered;
    double        nloops;

    if (!es->analyze || !planstate->instrument)
        return;

    if (which == 2)
        nfiltered = planstate->instrument->nfiltered2;
    else
        nfiltered = planstate->instrument->nfiltered1;
    nloops = planstate->instrument->nloops;

    /* In text mode, suppress zero counts; they're not interesting enough */
    if (nfiltered > 0 || es->format != EXPLAIN_FORMAT_TEXT)
    {
        if (nloops > 0)
            ExplainPropertyFloat(qlabel, nfiltered / nloops, 0, es);
        else
            ExplainPropertyFloat(qlabel, 0.0, 0, es);
    }
}

/*
 * Show extra information for a ForeignScan node.
 */
static void
show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
{
    FdwRoutine *fdwroutine = fsstate->fdwroutine;

    /* Let the FDW emit whatever fields it wants */
    if (((ForeignScan *) fsstate->ss.ps.plan)->operation != CMD_SELECT)
    {
        if (fdwroutine->ExplainDirectModify != NULL)
            fdwroutine->ExplainDirectModify(fsstate, es);
    }
    else
    {
        if (fdwroutine->ExplainForeignScan != NULL)
            fdwroutine->ExplainForeignScan(fsstate, es);
    }
}

/*
 * Fetch the name of an index in an EXPLAIN
 *
 * We allow plugins to get control here so that plans involving hypothetical
 * indexes can be explained.
 */
static const char *
explain_get_index_name(Oid indexId)
{
    const char *result;

    if (explain_get_index_name_hook)
        result = (*explain_get_index_name_hook) (indexId);
    else
        result = NULL;
    if (result == NULL)
    {
        /* default behavior: look in the catalogs and quote it */
        result = get_rel_name(indexId);
        if (result == NULL)
            elog(ERROR, "cache lookup failed for index %u", indexId);
        result = quote_identifier(result);
    }
    return result;
}

/*
 * Show buffer usage details.
 */
static void
show_buffer_usage(ExplainState *es, const BufferUsage *usage)
{// #lizard forgives
    if (es->format == EXPLAIN_FORMAT_TEXT)
    {
        bool        has_shared = (usage->shared_blks_hit > 0 ||
                                  usage->shared_blks_read > 0 ||
                                  usage->shared_blks_dirtied > 0 ||
                                  usage->shared_blks_written > 0);
        bool        has_local = (usage->local_blks_hit > 0 ||
                                 usage->local_blks_read > 0 ||
                                 usage->local_blks_dirtied > 0 ||
                                 usage->local_blks_written > 0);
        bool        has_temp = (usage->temp_blks_read > 0 ||
                                usage->temp_blks_written > 0);
        bool        has_timing = (!INSTR_TIME_IS_ZERO(usage->blk_read_time) ||
                                  !INSTR_TIME_IS_ZERO(usage->blk_write_time));

        /* Show only positive counter values. */
        if (has_shared || has_local || has_temp)
        {
            appendStringInfoSpaces(es->str, es->indent * 2);
            appendStringInfoString(es->str, "Buffers:");

            if (has_shared)
            {
                appendStringInfoString(es->str, " shared");
                if (usage->shared_blks_hit > 0)
                    appendStringInfo(es->str, " hit=%ld",
                                     usage->shared_blks_hit);
                if (usage->shared_blks_read > 0)
                    appendStringInfo(es->str, " read=%ld",
                                     usage->shared_blks_read);
                if (usage->shared_blks_dirtied > 0)
                    appendStringInfo(es->str, " dirtied=%ld",
                                     usage->shared_blks_dirtied);
                if (usage->shared_blks_written > 0)
                    appendStringInfo(es->str, " written=%ld",
                                     usage->shared_blks_written);
                if (has_local || has_temp)
                    appendStringInfoChar(es->str, ',');
            }
            if (has_local)
            {
                appendStringInfoString(es->str, " local");
                if (usage->local_blks_hit > 0)
                    appendStringInfo(es->str, " hit=%ld",
                                     usage->local_blks_hit);
                if (usage->local_blks_read > 0)
                    appendStringInfo(es->str, " read=%ld",
                                     usage->local_blks_read);
                if (usage->local_blks_dirtied > 0)
                    appendStringInfo(es->str, " dirtied=%ld",
                                     usage->local_blks_dirtied);
                if (usage->local_blks_written > 0)
                    appendStringInfo(es->str, " written=%ld",
                                     usage->local_blks_written);
                if (has_temp)
                    appendStringInfoChar(es->str, ',');
            }
            if (has_temp)
            {
                appendStringInfoString(es->str, " temp");
                if (usage->temp_blks_read > 0)
                    appendStringInfo(es->str, " read=%ld",
                                     usage->temp_blks_read);
                if (usage->temp_blks_written > 0)
                    appendStringInfo(es->str, " written=%ld",
                                     usage->temp_blks_written);
            }
            appendStringInfoChar(es->str, '\n');
        }

        /* As above, show only positive counter values. */
        if (has_timing)
        {
            appendStringInfoSpaces(es->str, es->indent * 2);
            appendStringInfoString(es->str, "I/O Timings:");
            if (!INSTR_TIME_IS_ZERO(usage->blk_read_time))
                appendStringInfo(es->str, " read=%0.3f",
                                 INSTR_TIME_GET_MILLISEC(usage->blk_read_time));
            if (!INSTR_TIME_IS_ZERO(usage->blk_write_time))
                appendStringInfo(es->str, " write=%0.3f",
                                 INSTR_TIME_GET_MILLISEC(usage->blk_write_time));
            appendStringInfoChar(es->str, '\n');
        }
    }
    else
    {
        ExplainPropertyLong("Shared Hit Blocks", usage->shared_blks_hit, es);
        ExplainPropertyLong("Shared Read Blocks", usage->shared_blks_read, es);
        ExplainPropertyLong("Shared Dirtied Blocks", usage->shared_blks_dirtied, es);
        ExplainPropertyLong("Shared Written Blocks", usage->shared_blks_written, es);
        ExplainPropertyLong("Local Hit Blocks", usage->local_blks_hit, es);
        ExplainPropertyLong("Local Read Blocks", usage->local_blks_read, es);
        ExplainPropertyLong("Local Dirtied Blocks", usage->local_blks_dirtied, es);
        ExplainPropertyLong("Local Written Blocks", usage->local_blks_written, es);
        ExplainPropertyLong("Temp Read Blocks", usage->temp_blks_read, es);
        ExplainPropertyLong("Temp Written Blocks", usage->temp_blks_written, es);
        if (track_io_timing)
        {
            ExplainPropertyFloat("I/O Read Time", INSTR_TIME_GET_MILLISEC(usage->blk_read_time), 3, es);
            ExplainPropertyFloat("I/O Write Time", INSTR_TIME_GET_MILLISEC(usage->blk_write_time), 3, es);
        }
    }
}

/*
 * Add some additional details about an IndexScan or IndexOnlyScan
 */
static void
ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
                        ExplainState *es)
{
    const char *indexname = explain_get_index_name(indexid);

    if (es->format == EXPLAIN_FORMAT_TEXT)
    {
        if (ScanDirectionIsBackward(indexorderdir))
            appendStringInfoString(es->str, " Backward");
        appendStringInfo(es->str, " using %s", indexname);
    }
    else
    {
        const char *scandir;

        switch (indexorderdir)
        {
            case BackwardScanDirection:
                scandir = "Backward";
                break;
            case NoMovementScanDirection:
                scandir = "NoMovement";
                break;
            case ForwardScanDirection:
                scandir = "Forward";
                break;
            default:
                scandir = "???";
                break;
        }
        ExplainPropertyText("Scan Direction", scandir, es);
        ExplainPropertyText("Index Name", indexname, es);
    }
}

/*
 * Show the target of a Scan node
 */
static void
ExplainScanTarget(Scan *plan, ExplainState *es)
{
    ExplainTargetRel((Plan *) plan, plan->scanrelid, es);
}

/*
 * Show the target of a ModifyTable node
 *
 * Here we show the nominal target (ie, the relation that was named in the
 * original query).  If the actual target(s) is/are different, we'll show them
 * in show_modifytable_info().
 */
static void
ExplainModifyTarget(ModifyTable *plan, ExplainState *es)
{
    ExplainTargetRel((Plan *) plan, plan->nominalRelation, es);
}

/*
 * Show the target relation of a scan or modify node
 */
static void
ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
{// #lizard forgives
    char       *objectname = NULL;
    char       *namespace = NULL;
    const char *objecttag = NULL;
    RangeTblEntry *rte;
    char       *refname;
#ifdef __OPENTENBASE__
    int         partidx = -1;
#endif

    rte = rt_fetch(rti, es->rtable);
    refname = (char *) list_nth(es->rtable_names, rti - 1);
    if (refname == NULL)
        refname = rte->eref->aliasname;

    switch (nodeTag(plan))
    {
        case T_SeqScan:
        case T_SampleScan:
        case T_IndexScan:
        case T_IndexOnlyScan:
        case T_BitmapHeapScan:
        case T_TidScan:
#ifdef __OPENTENBASE__
            {
                Scan *scan = (Scan*)plan;
                if(scan->ispartchild)
                    partidx = scan->childidx;
            }
#endif
        case T_ForeignScan:
        case T_CustomScan:
        case T_ModifyTable:
            /* Assert it's on a real relation */
            Assert(rte->rtekind == RTE_RELATION);
            objectname = get_rel_name(rte->relid);
            if (es->verbose)
                namespace = get_namespace_name(get_rel_namespace(rte->relid));
            objecttag = "Relation Name";
            break;
        case T_FunctionScan:
            {
                FunctionScan *fscan = (FunctionScan *) plan;

                /* Assert it's on a RangeFunction */
                Assert(rte->rtekind == RTE_FUNCTION);

                /*
                 * If the expression is still a function call of a single
                 * function, we can get the real name of the function.
                 * Otherwise, punt.  (Even if it was a single function call
                 * originally, the optimizer could have simplified it away.)
                 */
                if (list_length(fscan->functions) == 1)
                {
                    RangeTblFunction *rtfunc = (RangeTblFunction *) linitial(fscan->functions);

                    if (IsA(rtfunc->funcexpr, FuncExpr))
                    {
                        FuncExpr   *funcexpr = (FuncExpr *) rtfunc->funcexpr;
                        Oid            funcid = funcexpr->funcid;

                        objectname = get_func_name(funcid);
                        if (es->verbose)
                            namespace =
                                get_namespace_name(get_func_namespace(funcid));
                    }
                }
                objecttag = "Function Name";
            }
            break;
        case T_TableFuncScan:
            Assert(rte->rtekind == RTE_TABLEFUNC);
            objectname = "xmltable";
            objecttag = "Table Function Name";
            break;
        case T_ValuesScan:
            Assert(rte->rtekind == RTE_VALUES);
            break;
        case T_CteScan:
            /* Assert it's on a non-self-reference CTE */
            Assert(rte->rtekind == RTE_CTE);
            Assert(!rte->self_reference);
            objectname = rte->ctename;
            objecttag = "CTE Name";
            break;
        case T_NamedTuplestoreScan:
            Assert(rte->rtekind == RTE_NAMEDTUPLESTORE);
            objectname = rte->enrname;
            objecttag = "Tuplestore Name";
            break;
        case T_WorkTableScan:
            /* Assert it's on a self-reference CTE */
            Assert(rte->rtekind == RTE_CTE);
            Assert(rte->self_reference);
            objectname = rte->ctename;
            objecttag = "CTE Name";
            break;
        case T_RemoteQuery:
            /* get the object name from RTE itself */
            Assert(rte->rtekind == RTE_REMOTE_DUMMY);
            objectname = get_rel_name(rte->relid);
            objecttag = "RemoteQuery name";
            break;
        default:
            break;
    }

    if (es->format == EXPLAIN_FORMAT_TEXT)
    {
        appendStringInfoString(es->str, " on");
        if (namespace != NULL)
            appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
                             quote_identifier(objectname));
        else if (objectname != NULL)
            appendStringInfo(es->str, " %s", quote_identifier(objectname));
        if (objectname == NULL || strcmp(refname, objectname) != 0)
            appendStringInfo(es->str, " %s", quote_identifier(refname));
#ifdef __OPENTENBASE__
        if(partidx >= 0)
        {
            char * relname;
            appendStringInfo(es->str, " (partition sequence: %d", partidx);
            relname = GetPartitionName(rte->relid, partidx, false);
            appendStringInfo(es->str, ", name: %s)", relname);
            pfree(relname);
        }
#endif
    }
    else
    {
        if (objecttag != NULL && objectname != NULL)
            ExplainPropertyText(objecttag, objectname, es);
        if (namespace != NULL)
            ExplainPropertyText("Schema", namespace, es);
        ExplainPropertyText("Alias", refname, es);
#ifdef __OPENTENBASE__
        if(partidx >= 0)
        {
            char * relname;
            ExplainPropertyInteger("PartitionIndex", partidx, es);
            relname = GetPartitionName(rte->relid, partidx, false);
            ExplainPropertyText("PartitionName", relname, es);
            pfree(relname);
        }
#endif
    }
}

/*
 * Show extra information for a ModifyTable node
 *
 * We have three objectives here.  First, if there's more than one target
 * table or it's different from the nominal target, identify the actual
 * target(s).  Second, give FDWs a chance to display extra info about foreign
 * targets.  Third, show information about ON CONFLICT.
 */
static void
show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
                      ExplainState *es)
{// #lizard forgives
    ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
    const char *operation;
    const char *foperation;
    bool        labeltargets;
    int            j;
    List       *idxNames = NIL;
    ListCell   *lst;

    switch (node->operation)
    {
        case CMD_INSERT:
            operation = "Insert";
            foperation = "Foreign Insert";
            break;
        case CMD_UPDATE:
            operation = "Update";
            foperation = "Foreign Update";
            break;
        case CMD_DELETE:
            operation = "Delete";
            foperation = "Foreign Delete";
            break;
        default:
            operation = "???";
            foperation = "Foreign ???";
            break;
    }

    /* Should we explicitly label target relations? */
    labeltargets = (mtstate->mt_nplans > 1 ||
                    (mtstate->mt_nplans == 1 &&
                     mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));

    if (labeltargets)
        ExplainOpenGroup("Target Tables", "Target Tables", false, es);

    for (j = 0; j < mtstate->mt_nplans; j++)
    {
        ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
        FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;

        if (labeltargets)
        {
            /* Open a group for this target */
            ExplainOpenGroup("Target Table", NULL, true, es);

            /*
             * In text mode, decorate each target with operation type, so that
             * ExplainTargetRel's output of " on foo" will read nicely.
             */
            if (es->format == EXPLAIN_FORMAT_TEXT)
            {
                appendStringInfoSpaces(es->str, es->indent * 2);
                appendStringInfoString(es->str,
                                       fdwroutine ? foperation : operation);
            }

            /* Identify target */
            ExplainTargetRel((Plan *) node,
                             resultRelInfo->ri_RangeTableIndex,
                             es);

            if (es->format == EXPLAIN_FORMAT_TEXT)
            {
                appendStringInfoChar(es->str, '\n');
                es->indent++;
            }
        }

        /* Give FDW a chance if needed */
        if (!resultRelInfo->ri_usesFdwDirectModify &&
            fdwroutine != NULL &&
            fdwroutine->ExplainForeignModify != NULL)
        {
            List       *fdw_private = (List *) list_nth(node->fdwPrivLists, j);

            fdwroutine->ExplainForeignModify(mtstate,
                                             resultRelInfo,
                                             fdw_private,
                                             j,
                                             es);
        }

        if (labeltargets)
        {
            /* Undo the indentation we added in text format */
            if (es->format == EXPLAIN_FORMAT_TEXT)
                es->indent--;

            /* Close the group */
            ExplainCloseGroup("Target Table", NULL, true, es);
        }
    }

    /* Gather names of ON CONFLICT arbiter indexes */
    foreach(lst, node->arbiterIndexes)
    {
        char       *indexname = get_rel_name(lfirst_oid(lst));

        idxNames = lappend(idxNames, indexname);
    }

    if (node->onConflictAction != ONCONFLICT_NONE)
    {
        ExplainProperty("Conflict Resolution",
                        node->onConflictAction == ONCONFLICT_NOTHING ?
                        "NOTHING" : "UPDATE",
                        false, es);

        /*
         * Don't display arbiter indexes at all when DO NOTHING variant
         * implicitly ignores all conflicts
         */
        if (idxNames)
            ExplainPropertyList("Conflict Arbiter Indexes", idxNames, es);

        /* ON CONFLICT DO UPDATE WHERE qual is specially displayed */
        if (node->onConflictWhere)
        {
            show_upper_qual((List *) node->onConflictWhere, "Conflict Filter",
                            &mtstate->ps, ancestors, es);
            show_instrumentation_count("Rows Removed by Conflict Filter", 1, &mtstate->ps, es);
        }

        /* EXPLAIN ANALYZE display of actual outcome for each tuple proposed */
        if (es->analyze && mtstate->ps.instrument)
        {
            double        total;
            double        insert_path;
            double        other_path;

            InstrEndLoop(mtstate->mt_plans[0]->instrument);

            /* count the number of source rows */
            total = mtstate->mt_plans[0]->instrument->ntuples;
            other_path = mtstate->ps.instrument->nfiltered2;
            insert_path = total - other_path;

            ExplainPropertyFloat("Tuples Inserted", insert_path, 0, es);
            ExplainPropertyFloat("Conflicting Tuples", other_path, 0, es);
        }
    }

    if (labeltargets)
        ExplainCloseGroup("Target Tables", "Target Tables", false, es);
}

/*
 * Explain the constituent plans of a ModifyTable, Append, MergeAppend,
 * BitmapAnd, or BitmapOr node.
 *
 * The ancestors list should already contain the immediate parent of these
 * plans.
 *
 * Note: we don't actually need to examine the Plan list members, but
 * we need the list in order to determine the length of the PlanState array.
 */
static void
ExplainMemberNodes(List *plans, PlanState **planstates,
                   List *ancestors, ExplainState *es)
{
    int            nplans = list_length(plans);
    int            j;

    for (j = 0; j < nplans; j++)
        ExplainNode(planstates[j], ancestors,
                    "Member", NULL, es);
}

/*
 * Explain a list of SubPlans (or initPlans, which also use SubPlan nodes).
 *
 * The ancestors list should already contain the immediate parent of these
 * SubPlanStates.
 */
static void
ExplainSubPlans(List *plans, List *ancestors,
                const char *relationship, ExplainState *es)
{
    ListCell   *lst;

    foreach(lst, plans)
    {
        SubPlanState *sps = (SubPlanState *) lfirst(lst);
        SubPlan    *sp = sps->subplan;

        /*
         * There can be multiple SubPlan nodes referencing the same physical
         * subplan (same plan_id, which is its index in PlannedStmt.subplans).
         * We should print a subplan only once, so track which ones we already
         * printed.  This state must be global across the plan tree, since the
         * duplicate nodes could be in different plan nodes, eg both a bitmap
         * indexscan's indexqual and its parent heapscan's recheck qual.  (We
         * do not worry too much about which plan node we show the subplan as
         * attached to in such cases.)
         */
        if (bms_is_member(sp->plan_id, es->printed_subplans))
            continue;
        es->printed_subplans = bms_add_member(es->printed_subplans,
                                              sp->plan_id);

        ExplainNode(sps->planstate, ancestors,
                    relationship, sp->plan_name, es);
    }
}

/*
 * Explain a list of children of a CustomScan.
 */
static void
ExplainCustomChildren(CustomScanState *css, List *ancestors, ExplainState *es)
{
    ListCell   *cell;
    const char *label =
    (list_length(css->custom_ps) != 1 ? "children" : "child");

    foreach(cell, css->custom_ps)
        ExplainNode((PlanState *) lfirst(cell), ancestors, label, NULL, es);
}

/*
 * Explain a property, such as sort keys or targets, that takes the form of
 * a list of unlabeled items.  "data" is a list of C strings.
 */
void
ExplainPropertyList(const char *qlabel, List *data, ExplainState *es)
{
    ListCell   *lc;
    bool        first = true;

    switch (es->format)
    {
        case EXPLAIN_FORMAT_TEXT:
            appendStringInfoSpaces(es->str, es->indent * 2);
            appendStringInfo(es->str, "%s: ", qlabel);
            foreach(lc, data)
            {
                if (!first)
                    appendStringInfoString(es->str, ", ");
                appendStringInfoString(es->str, (const char *) lfirst(lc));
                first = false;
            }
            appendStringInfoChar(es->str, '\n');
            break;

        case EXPLAIN_FORMAT_XML:
            ExplainXMLTag(qlabel, X_OPENING, es);
            foreach(lc, data)
            {
                char       *str;

                appendStringInfoSpaces(es->str, es->indent * 2 + 2);
                appendStringInfoString(es->str, "<Item>");
                str = escape_xml((const char *) lfirst(lc));
                appendStringInfoString(es->str, str);
                pfree(str);
                appendStringInfoString(es->str, "</Item>\n");
            }
            ExplainXMLTag(qlabel, X_CLOSING, es);
            break;

        case EXPLAIN_FORMAT_JSON:
            ExplainJSONLineEnding(es);
            appendStringInfoSpaces(es->str, es->indent * 2);
            escape_json(es->str, qlabel);
            appendStringInfoString(es->str, ": [");
            foreach(lc, data)
            {
                if (!first)
                    appendStringInfoString(es->str, ", ");
                escape_json(es->str, (const char *) lfirst(lc));
                first = false;
            }
            appendStringInfoChar(es->str, ']');
            break;

        case EXPLAIN_FORMAT_YAML:
            ExplainYAMLLineStarting(es);
            appendStringInfo(es->str, "%s: ", qlabel);
            foreach(lc, data)
            {
                appendStringInfoChar(es->str, '\n');
                appendStringInfoSpaces(es->str, es->indent * 2 + 2);
                appendStringInfoString(es->str, "- ");
                escape_yaml(es->str, (const char *) lfirst(lc));
            }
            break;
    }
}

/*
 * Explain a property that takes the form of a list of unlabeled items within
 * another list.  "data" is a list of C strings.
 */
void
ExplainPropertyListNested(const char *qlabel, List *data, ExplainState *es)
{
    ListCell   *lc;
    bool        first = true;

    switch (es->format)
    {
        case EXPLAIN_FORMAT_TEXT:
        case EXPLAIN_FORMAT_XML:
            ExplainPropertyList(qlabel, data, es);
            return;

        case EXPLAIN_FORMAT_JSON:
            ExplainJSONLineEnding(es);
            appendStringInfoSpaces(es->str, es->indent * 2);
            appendStringInfoChar(es->str, '[');
            foreach(lc, data)
            {
                if (!first)
                    appendStringInfoString(es->str, ", ");
                escape_json(es->str, (const char *) lfirst(lc));
                first = false;
            }
            appendStringInfoChar(es->str, ']');
            break;

        case EXPLAIN_FORMAT_YAML:
            ExplainYAMLLineStarting(es);
            appendStringInfoString(es->str, "- [");
            foreach(lc, data)
            {
                if (!first)
                    appendStringInfoString(es->str, ", ");
                escape_yaml(es->str, (const char *) lfirst(lc));
                first = false;
            }
            appendStringInfoChar(es->str, ']');
            break;
    }
}

/* split a string based on a delimiter */
static char **
StrSplit(const char *str, const char *delimiter, int *n)
{
	char *tmp = NULL;
	char **rtn = NULL;
	char *token = NULL;

	*n = 0;
	if (!str)
		return NULL;

	/* copy str to tmp as strtok will mangle the string */
	tmp = pstrdup(str);

	if (!strlen(tmp) || !delimiter || !strlen(delimiter))
	{
		*n = 1;
		rtn = (char **) palloc(*n * sizeof(char *));
		rtn[0] = pstrdup(tmp);
		pfree(tmp);
		return rtn;
	}

	token = strtok(tmp, delimiter);
	while (token != NULL)
	{
		if (*n < 1)
		{
			rtn = (char **) palloc(sizeof(char *));
		}
		else
		{
			rtn = (char **) repalloc(rtn, (*n + 1) * sizeof(char *));
		}

		rtn[*n] = NULL;
		rtn[*n] = pstrdup(token);
		*n = *n + 1;

		token = strtok(NULL, delimiter);
	}

	pfree(tmp);
	return rtn;
}

static void
DealRemoteJson(StringInfo explainResult, const char *value, int spaceLen)
{
	int i = 0;
	int num = 0;
	char **result = NULL;
	result = StrSplit(value, "\n", &num);
	for (i = 0; i < num; i++)
	{
		if (i > 0)
		{
			appendStringInfo(explainResult, "\n");
			appendStringInfoSpaces(explainResult, spaceLen);
		}
		appendStringInfo(explainResult, "%s", result[i]);			
		pfree(result[i]);
	}
	if (result)
		pfree(result);
}

/*
 * Explain a simple property.
 *
 * If "numeric" is true, the value is a number (or other value that
 * doesn't need quoting in JSON).
 *
 * This usually should not be invoked directly, but via one of the datatype
 * specific routines ExplainPropertyText, ExplainPropertyInteger, etc.
 */
static void
ExplainProperty(const char *qlabel, const char *value, bool numeric,
                ExplainState *es)
{
    switch (es->format)
    {
        case EXPLAIN_FORMAT_TEXT:
            appendStringInfoSpaces(es->str, es->indent * 2);
            appendStringInfo(es->str, "%s: %s\n", qlabel, value);
            break;

        case EXPLAIN_FORMAT_XML:
            {
                char       *str;

                appendStringInfoSpaces(es->str, es->indent * 2);
                ExplainXMLTag(qlabel, X_OPENING | X_NOWHITESPACE, es);
                str = escape_xml(value);
                appendStringInfoString(es->str, str);
                pfree(str);
                ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es);
                appendStringInfoChar(es->str, '\n');
            }
            break;

        case EXPLAIN_FORMAT_JSON:
            ExplainJSONLineEnding(es);
            appendStringInfoSpaces(es->str, es->indent * 2);
            escape_json(es->str, qlabel);
            appendStringInfoString(es->str, ": ");
            if (numeric)
                appendStringInfoString(es->str, value);
            else
                escape_json(es->str, value);
            break;

        case EXPLAIN_FORMAT_YAML:
            ExplainYAMLLineStarting(es);
            appendStringInfo(es->str, "%s: ", qlabel);
            if (numeric)
                appendStringInfoString(es->str, value);
            else
                escape_yaml(es->str, value);
            break;
    }
}

/*
 * Explain a string-valued property.
 */
void
ExplainPropertyText(const char *qlabel, const char *value, ExplainState *es)
{
    ExplainProperty(qlabel, value, false, es);
}

/*
 * Explain an integer-valued property.
 */
void
ExplainPropertyInteger(const char *qlabel, int value, ExplainState *es)
{
    char        buf[32];

    snprintf(buf, sizeof(buf), "%d", value);
    ExplainProperty(qlabel, buf, true, es);
}

/*
 * Explain a long-integer-valued property.
 */
void
ExplainPropertyLong(const char *qlabel, long value, ExplainState *es)
{
    char        buf[32];

    snprintf(buf, sizeof(buf), "%ld", value);
    ExplainProperty(qlabel, buf, true, es);
}

/*
 * Explain a float-valued property, using the specified number of
 * fractional digits.
 */
void
ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
                     ExplainState *es)
{
    char        buf[256];

    snprintf(buf, sizeof(buf), "%.*f", ndigits, value);
    ExplainProperty(qlabel, buf, true, es);
}

/*
 * Explain a bool-valued property.
 */
void
ExplainPropertyBool(const char *qlabel, bool value, ExplainState *es)
{
    ExplainProperty(qlabel, value ? "true" : "false", true, es);
}

/*
 * Open a group of related objects.
 *
 * objtype is the type of the group object, labelname is its label within
 * a containing object (if any).
 *
 * If labeled is true, the group members will be labeled properties,
 * while if it's false, they'll be unlabeled objects.
 */
static void
ExplainOpenGroup(const char *objtype, const char *labelname,
                 bool labeled, ExplainState *es)
{// #lizard forgives
    switch (es->format)
    {
        case EXPLAIN_FORMAT_TEXT:
            /* nothing to do */
            break;

        case EXPLAIN_FORMAT_XML:
            ExplainXMLTag(objtype, X_OPENING, es);
            es->indent++;
            break;

        case EXPLAIN_FORMAT_JSON:
            ExplainJSONLineEnding(es);
            appendStringInfoSpaces(es->str, 2 * es->indent);
            if (labelname)
            {
                escape_json(es->str, labelname);
                appendStringInfoString(es->str, ": ");
            }
            appendStringInfoChar(es->str, labeled ? '{' : '[');

            /*
             * In JSON format, the grouping_stack is an integer list.  0 means
             * we've emitted nothing at this grouping level, 1 means we've
             * emitted something (and so the next item needs a comma). See
             * ExplainJSONLineEnding().
             */
            es->grouping_stack = lcons_int(0, es->grouping_stack);
            es->indent++;
            break;

        case EXPLAIN_FORMAT_YAML:

            /*
             * In YAML format, the grouping stack is an integer list.  0 means
             * we've emitted nothing at this grouping level AND this grouping
             * level is unlabelled and must be marked with "- ".  See
             * ExplainYAMLLineStarting().
             */
            ExplainYAMLLineStarting(es);
            if (labelname)
            {
                appendStringInfo(es->str, "%s: ", labelname);
                es->grouping_stack = lcons_int(1, es->grouping_stack);
            }
            else
            {
                appendStringInfoString(es->str, "- ");
                es->grouping_stack = lcons_int(0, es->grouping_stack);
            }
            es->indent++;
            break;
    }
}

/*
 * Close a group of related objects.
 * Parameters must match the corresponding ExplainOpenGroup call.
 */
static void
ExplainCloseGroup(const char *objtype, const char *labelname,
                  bool labeled, ExplainState *es)
{
    switch (es->format)
    {
        case EXPLAIN_FORMAT_TEXT:
            /* nothing to do */
            break;

        case EXPLAIN_FORMAT_XML:
            es->indent--;
            ExplainXMLTag(objtype, X_CLOSING, es);
            break;

        case EXPLAIN_FORMAT_JSON:
            es->indent--;
            appendStringInfoChar(es->str, '\n');
            appendStringInfoSpaces(es->str, 2 * es->indent);
            appendStringInfoChar(es->str, labeled ? '}' : ']');
            es->grouping_stack = list_delete_first(es->grouping_stack);
            break;

        case EXPLAIN_FORMAT_YAML:
            es->indent--;
            es->grouping_stack = list_delete_first(es->grouping_stack);
            break;
    }
}

/*
 * Emit a "dummy" group that never has any members.
 *
 * objtype is the type of the group object, labelname is its label within
 * a containing object (if any).
 */
static void
ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es)
{
    switch (es->format)
    {
        case EXPLAIN_FORMAT_TEXT:
            /* nothing to do */
            break;

        case EXPLAIN_FORMAT_XML:
            ExplainXMLTag(objtype, X_CLOSE_IMMEDIATE, es);
            break;

        case EXPLAIN_FORMAT_JSON:
            ExplainJSONLineEnding(es);
            appendStringInfoSpaces(es->str, 2 * es->indent);
            if (labelname)
            {
                escape_json(es->str, labelname);
                appendStringInfoString(es->str, ": ");
            }
            escape_json(es->str, objtype);
            break;

        case EXPLAIN_FORMAT_YAML:
            ExplainYAMLLineStarting(es);
            if (labelname)
            {
                escape_yaml(es->str, labelname);
                appendStringInfoString(es->str, ": ");
            }
            else
            {
                appendStringInfoString(es->str, "- ");
            }
            escape_yaml(es->str, objtype);
            break;
    }
}

/*
 * Emit the start-of-output boilerplate.
 *
 * This is just enough different from processing a subgroup that we need
 * a separate pair of subroutines.
 */
void
ExplainBeginOutput(ExplainState *es)
{
    switch (es->format)
    {
        case EXPLAIN_FORMAT_TEXT:
            /* nothing to do */
            break;

        case EXPLAIN_FORMAT_XML:
            appendStringInfoString(es->str,
                                   "<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n");
            es->indent++;
            break;

        case EXPLAIN_FORMAT_JSON:
            /* top-level structure is an array of plans */
            appendStringInfoChar(es->str, '[');
            es->grouping_stack = lcons_int(0, es->grouping_stack);
            es->indent++;
            break;

        case EXPLAIN_FORMAT_YAML:
            es->grouping_stack = lcons_int(0, es->grouping_stack);
            break;
    }
}

/*
 * Emit the end-of-output boilerplate.
 */
void
ExplainEndOutput(ExplainState *es)
{
    switch (es->format)
    {
        case EXPLAIN_FORMAT_TEXT:
            /* nothing to do */
            break;

        case EXPLAIN_FORMAT_XML:
            es->indent--;
            appendStringInfoString(es->str, "</explain>");
            break;

        case EXPLAIN_FORMAT_JSON:
            es->indent--;
            appendStringInfoString(es->str, "\n]");
            es->grouping_stack = list_delete_first(es->grouping_stack);
            break;

        case EXPLAIN_FORMAT_YAML:
            es->grouping_stack = list_delete_first(es->grouping_stack);
            break;
    }
}

/*
 * Put an appropriate separator between multiple plans
 */
void
ExplainSeparatePlans(ExplainState *es)
{
    switch (es->format)
    {
        case EXPLAIN_FORMAT_TEXT:
            /* add a blank line */
            appendStringInfoChar(es->str, '\n');
            break;

        case EXPLAIN_FORMAT_XML:
        case EXPLAIN_FORMAT_JSON:
        case EXPLAIN_FORMAT_YAML:
            /* nothing to do */
            break;
    }
}

#ifdef PGXC
/*
 * Emit execution node list number.
 */
static void
ExplainExecNodes(ExecNodes *en, ExplainState *es)
{
    int primary_node_count = en ? list_length(en->primarynodelist) : 0;
    int node_count = en ? list_length(en->nodeList) : 0;

    if (!es->num_nodes)
        return;

    if (es->format == EXPLAIN_FORMAT_TEXT)
    {
        appendStringInfo(es->str, " (primary node count=%d, node count=%d)",
                         primary_node_count, node_count);
    }
    else
    {
        ExplainPropertyInteger("Primary node count", primary_node_count, es);
        ExplainPropertyInteger("Node count", node_count, es);
    }
}

/*
 * Emit remote query planning details
 */
static void
ExplainRemoteQuery(RemoteQuery *plan, PlanState *planstate, List *ancestors, ExplainState *es)
{// #lizard forgives
    ExecNodes    *en = plan->exec_nodes;
    /* add names of the nodes if they exist */

    if (en && es->nodes)
    {
        StringInfo node_names = makeStringInfo();
        ListCell *lcell;
        char    *sep;
        int        node_no;
        if (en->primarynodelist)
        {
            sep = "";
            foreach(lcell, en->primarynodelist)
            {
                node_no = lfirst_int(lcell);
                appendStringInfo(node_names, "%s%s", sep,
                                    get_pgxc_nodename(PGXCNodeGetNodeOid(node_no, PGXC_NODE_DATANODE)));
                sep = ", ";
            }
            ExplainPropertyText("Primary node/s", node_names->data, es);
        }
        if (en->nodeList)
        {
            resetStringInfo(node_names);
            sep = "";
            foreach(lcell, en->nodeList)
            {
                node_no = lfirst_int(lcell);
                appendStringInfo(node_names, "%s%s", sep,
                                    get_pgxc_nodename(PGXCNodeGetNodeOid(node_no, PGXC_NODE_DATANODE)));
#ifdef __OPENTENBASE__
                if (explain_query_analyze)
                {
                    char *analyze_info = GetAnalyzeInfo(node_no, plan->sql_statement);
                    if (analyze_info)
                    {
                        appendStringInfo(node_names, "(%s)", analyze_info);
                        pfree(analyze_info);
                    }
                }
#endif
                sep = ", ";
            }
            ExplainPropertyText("Node/s", node_names->data, es);
        }
    }

	/*
	 * if required, skip executing remote query, this
	 * is happened when a backend report planstate it
	 * processing, shouldn't execute it again.
	 */
	if (es->skip_remote_query)
		return;

    if (en && en->en_expr)
        show_expression((Node *)en->en_expr, "Node expr", planstate, ancestors,
                        es->verbose, es);

    /* Remote query statement */
    if (es->verbose)
        ExplainPropertyText("Remote query", plan->sql_statement, es);

    if (!es->analyze)
    {
        RemoteQuery *step = makeNode(RemoteQuery);
        StringInfoData explainQuery;
        StringInfoData explainResult;
        EState *estate;
        RemoteQueryState *node;
        Var *dummy;
        MemoryContext oldcontext;
        TupleTableSlot *result;
        bool firstline = true;

        initStringInfo(&explainQuery);
        initStringInfo(&explainResult);

        if (es->format == EXPLAIN_FORMAT_TEXT)
        {
            if (es->indent)
            {
                appendStringInfoSpaces(es->str, es->indent * 2);
                appendStringInfoString(es->str, "->  ");
                es->indent += 2;
            }
        }

        appendStringInfo(&explainQuery, "EXPLAIN (");
        switch (es->format)
        {
            case EXPLAIN_FORMAT_TEXT:
                appendStringInfo(&explainQuery, "FORMAT TEXT");
                break;
            case EXPLAIN_FORMAT_YAML:
                appendStringInfo(&explainQuery, "FORMAT YAML");
                break;
            case EXPLAIN_FORMAT_JSON:
                appendStringInfo(&explainQuery, "FORMAT JSON");
                break;
            case EXPLAIN_FORMAT_XML:
                appendStringInfo(&explainQuery, "FORMAT XML");
                break;
        }
        appendStringInfo(&explainQuery, ", VERBOSE %s", es->verbose ? "ON" : "OFF");
        appendStringInfo(&explainQuery, ", COSTS %s", es->costs ? "ON" : "OFF");
        appendStringInfo(&explainQuery, ", TIMING %s", es->timing ? "ON" : "OFF");
        appendStringInfo(&explainQuery, ", BUFFERS %s", es->buffers ? "ON" : "OFF");
        appendStringInfo(&explainQuery, ") %s", plan->sql_statement);

        step->sql_statement = explainQuery.data;
        step->combine_type = COMBINE_TYPE_NONE;
        step->exec_nodes = copyObject(plan->exec_nodes);
        step->exec_nodes->primarynodelist = NIL;

        if (plan->exec_nodes && plan->exec_nodes->nodeList)
            step->exec_nodes->nodeList =
                list_make1_int(linitial_int(plan->exec_nodes->nodeList));

        step->force_autocommit = true;
        step->exec_type = EXEC_ON_DATANODES;

        dummy = makeVar(1, 1, TEXTOID, -1, InvalidOid, 0);
        step->scan.plan.targetlist = lappend(step->scan.plan.targetlist,
                makeTargetEntry((Expr *) dummy, 1, "QUERY PLAN", false));

        estate = planstate->state;
        oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);

		node = ExecInitRemoteQuery(step, estate, EXEC_FLAG_EXPLAIN_ONLY);
        MemoryContextSwitchTo(oldcontext);
        result = ExecRemoteQuery((PlanState *) node);
        while (result != NULL && !TupIsNull(result))
        {
            Datum     value;
            bool    isnull;
            value = slot_getattr(result, 1, &isnull);
            if (!isnull)
            {
				if (es->format == EXPLAIN_FORMAT_JSON)
				{
					if (!firstline)
						appendStringInfo(&explainResult, "\n");
					DealRemoteJson(&explainResult, TextDatumGetCString(value), 2 * es->indent);
				}
				else
				{
                if (!firstline)
                    appendStringInfoSpaces(&explainResult, 2 * es->indent);
                appendStringInfo(&explainResult, "%s\n", TextDatumGetCString(value));
				}
                firstline = false;
            }

            /* fetch next */
            result = ExecRemoteQuery((PlanState *) node);
        }
        ExecEndRemoteQuery(node);

        if (es->format == EXPLAIN_FORMAT_TEXT)
            appendStringInfo(es->str, "%s", explainResult.data);
		else if (es->format == EXPLAIN_FORMAT_JSON)
		{
			appendStringInfoChar(es->str, '\n');
			appendStringInfoSpaces(es->str, es->indent * 2);
			appendStringInfo(es->str, "%s: %s", "\"Remote plan\"", explainResult.data);
		}
        else
		{
            ExplainPropertyText("Remote plan", explainResult.data, es);
    }
}
}
#endif

/*
 * Emit opening or closing XML tag.
 *
 * "flags" must contain X_OPENING, X_CLOSING, or X_CLOSE_IMMEDIATE.
 * Optionally, OR in X_NOWHITESPACE to suppress the whitespace we'd normally
 * add.
 *
 * XML restricts tag names more than our other output formats, eg they can't
 * contain white space or slashes.  Replace invalid characters with dashes,
 * so that for example "I/O Read Time" becomes "I-O-Read-Time".
 */
static void
ExplainXMLTag(const char *tagname, int flags, ExplainState *es)
{
    const char *s;
    const char *valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.";

    if ((flags & X_NOWHITESPACE) == 0)
        appendStringInfoSpaces(es->str, 2 * es->indent);
    appendStringInfoCharMacro(es->str, '<');
    if ((flags & X_CLOSING) != 0)
        appendStringInfoCharMacro(es->str, '/');
    for (s = tagname; *s; s++)
        appendStringInfoChar(es->str, strchr(valid, *s) ? *s : '-');
    if ((flags & X_CLOSE_IMMEDIATE) != 0)
        appendStringInfoString(es->str, " /");
    appendStringInfoCharMacro(es->str, '>');
    if ((flags & X_NOWHITESPACE) == 0)
        appendStringInfoCharMacro(es->str, '\n');
}

/*
 * Emit a JSON line ending.
 *
 * JSON requires a comma after each property but the last.  To facilitate this,
 * in JSON format, the text emitted for each property begins just prior to the
 * preceding line-break (and comma, if applicable).
 */
static void
ExplainJSONLineEnding(ExplainState *es)
{
    Assert(es->format == EXPLAIN_FORMAT_JSON);
    if (linitial_int(es->grouping_stack) != 0)
        appendStringInfoChar(es->str, ',');
    else
        linitial_int(es->grouping_stack) = 1;
    appendStringInfoChar(es->str, '\n');
}

/*
 * Indent a YAML line.
 *
 * YAML lines are ordinarily indented by two spaces per indentation level.
 * The text emitted for each property begins just prior to the preceding
 * line-break, except for the first property in an unlabelled group, for which
 * it begins immediately after the "- " that introduces the group.  The first
 * property of the group appears on the same line as the opening "- ".
 */
static void
ExplainYAMLLineStarting(ExplainState *es)
{
    Assert(es->format == EXPLAIN_FORMAT_YAML);
    if (linitial_int(es->grouping_stack) == 0)
    {
        linitial_int(es->grouping_stack) = 1;
    }
    else
    {
        appendStringInfoChar(es->str, '\n');
        appendStringInfoSpaces(es->str, es->indent * 2);
    }
}

/*
 * YAML is a superset of JSON; unfortunately, the YAML quoting rules are
 * ridiculously complicated -- as documented in sections 5.3 and 7.3.3 of
 * http://yaml.org/spec/1.2/spec.html -- so we chose to just quote everything.
 * Empty strings, strings with leading or trailing whitespace, and strings
 * containing a variety of special characters must certainly be quoted or the
 * output is invalid; and other seemingly harmless strings like "0xa" or
 * "true" must be quoted, lest they be interpreted as a hexadecimal or Boolean
 * constant rather than a string.
 */
static void
escape_yaml(StringInfo buf, const char *str)
{
    escape_json(buf, str);
}
